diff options
-rw-r--r-- | cpp/000organization | 173 | ||||
-rw-r--r-- | cpp/002trace | 167 | ||||
-rw-r--r-- | cpp/009includes | 4 | ||||
-rw-r--r-- | cpp/010vm | 10 | ||||
-rw-r--r-- | cpp/011load | 2 | ||||
-rw-r--r-- | cpp/012run | 32 | ||||
-rw-r--r-- | cpp/017record | 6 | ||||
-rw-r--r-- | cpp/018address | 8 | ||||
-rw-r--r-- | cpp/019array | 11 | ||||
-rw-r--r-- | cpp/020call | 3 | ||||
-rw-r--r-- | cpp/021call_ingredient | 4 | ||||
-rw-r--r-- | cpp/022call_reply | 2 | ||||
-rw-r--r-- | cpp/makefile | 2 |
13 files changed, 223 insertions, 201 deletions
diff --git a/cpp/000organization b/cpp/000organization index ab1cee79..6c513809 100644 --- a/cpp/000organization +++ b/cpp/000organization @@ -1,86 +1,89 @@ -// 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. +//: 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". +//: A simple tool will 'tangle' these files according to the directives, though +//: it'll drop these comments starting with a '//:' prefix that only make sense +//: in the context of layers. +//: +//: 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 @@ -102,5 +105,5 @@ void setup() { // End Setup } -// Without directives or with the :(code) directive, lines get added at the -// end. +//: Without directives or with the :(code) directive, lines get added at the +//: end. diff --git a/cpp/002trace b/cpp/002trace index b4d0cfca..dd07beed 100644 --- a/cpp/002trace +++ b/cpp/002trace @@ -1,87 +1,86 @@ -// 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) +//: 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 +//: We try 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 diff --git a/cpp/009includes b/cpp/009includes index 5c9a8d65..c9f96288 100644 --- a/cpp/009includes +++ b/cpp/009includes @@ -1,5 +1,5 @@ -// Some common includes needed all over the place. -// More tightly-targeted includes show up in other files. +//: Some common includes needed all over the place. +//: More tightly-targeted includes show up in other files. :(before "End Includes") #include<assert.h> diff --git a/cpp/010vm b/cpp/010vm index 57f0be1f..53496fde 100644 --- a/cpp/010vm +++ b/cpp/010vm @@ -56,7 +56,7 @@ unordered_map<int, int> Memory; Memory.clear(); :(after "Types") -// Types encode how the numbers stored in different parts of memory are +// Mu 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. @@ -74,14 +74,14 @@ void setup_types() { Type.clear(); Type_number.clear(); Type_number["literal"] = 0; Next_type_number = 1; - // Mu Types. + // Mu Types Initialization. int integer = Type_number["integer"] = Next_type_number++; Type[integer].size = 1; int address = Type_number["address"] = Next_type_number++; Type[address].size = 1; int boolean = Type_number["boolean"] = Next_type_number++; Type[boolean].size = 1; - // End Mu Types. + // End Mu Types Initialization. } :(before "End Setup") setup_types(); @@ -127,8 +127,10 @@ void setup_recipes() { +//: Helpers + :(code) -// Helpers +// indent members to avoid generating prototypes for them instruction::instruction() :is_label(false), operation(IDLE) {} void instruction::clear() { is_label=false; label.clear(); operation=IDLE; ingredients.clear(); products.clear(); } diff --git a/cpp/011load b/cpp/011load index 707f9e59..0891a247 100644 --- a/cpp/011load +++ b/cpp/011load @@ -1,4 +1,4 @@ -// It's often convenient to express recipes in a textual fashion. +//: It's often convenient to express recipes in a textual fashion. :(scenarios add_recipes) :(scenario first_recipe) recipe main [ diff --git a/cpp/012run b/cpp/012run index 42b5d477..3d1395a9 100644 --- a/cpp/012run +++ b/cpp/012run @@ -19,7 +19,7 @@ recipe main [ :(before "End Types") // Book-keeping while running a recipe. -// Later layers will change this. +//: Later layers will change this. struct routine { recipe_number running_recipe; size_t running_at; @@ -27,11 +27,6 @@ struct routine { }; :(code) -void run(string form) { - vector<recipe_number> recipes_added = add_recipes(form); - run(recipes_added.front()); -} - void run(recipe_number r) { run(routine(r)); } @@ -59,9 +54,9 @@ void run(routine rr) { } } -// Some helpers. -// We'll need to override these later as we change the definition of routine. -// Important that they return referrences into the routine. +//: Some helpers. +//: We'll need to override these later as we change the definition of routine. +//: Important that they return referrences into the routine. inline size_t& running_at(routine& rr) { return rr.running_at; } @@ -92,6 +87,25 @@ if (argc > 1) { dump_memory(); } +//: helper for tests + +:(before "End Globals") +vector<recipe_number> recipes_added_by_test; + +:(code) +void run(string form) { + vector<recipe_number> tmp = add_recipes(form); + recipes_added_by_test.insert(recipes_added_by_test.end(), tmp.begin(), tmp.end()); + run(recipes_added_by_test.front()); +} + +:(before "End Setup") +for (size_t i = 0; i < recipes_added_by_test.size(); ++i) { + Recipe_number.erase(Recipe[recipes_added_by_test[i]].name); + Recipe.erase(recipes_added_by_test[i]); +} +recipes_added_by_test.clear(); + :(code) vector<int> read_memory(reagent x) { //? cout << "read_memory: " << x.to_string() << '\n'; //? 1 diff --git a/cpp/017record b/cpp/017record index d64fdcb6..f42531be 100644 --- a/cpp/017record +++ b/cpp/017record @@ -1,5 +1,5 @@ -// Support for records. -:(before "End Mu Types") +//: Support for records. +:(before "End Mu Types Initialization") // We'll use this record as a running example, with two integer fields int point = Type_number["point"] = Next_type_number++; Type[point].size = 2; @@ -73,7 +73,7 @@ recipe main [ +run: product 0 is 35 +mem: storing in location 15 -:(before "End Mu Types") +:(before "End Mu Types Initialization") // A more complex record, containing another record. int point_integer = Type_number["point-integer"] = Next_type_number++; Type[point_integer].size = 2; diff --git a/cpp/018address b/cpp/018address index bdf10685..cd4f180e 100644 --- a/cpp/018address +++ b/cpp/018address @@ -1,5 +1,6 @@ +//: Instructions can read from addresses pointing at other locations using the +//: 'deref' property. :(scenario "copy_indirect") -# Instructions can read from addresses pointing at other locations using the 'deref' property. recipe main [ 1:address:integer <- copy 2:literal 2:integer <- copy 34:literal @@ -29,8 +30,9 @@ vector<int> read_memory(reagent x) { return result; } +//: similarly, write to addresses pointing at other locations using the +//: 'deref' property :(scenario "store_indirect") -# similarly, write to addresses pointing at other locations using the 'deref' property recipe main [ 1:address:integer <- copy 2:literal 1:address:integer/deref <- copy 34:literal @@ -95,8 +97,8 @@ reagent deref(reagent x) { return result; } +//: 'get' can read from record address :(scenario "get_indirect") -# 'get' can read from record address recipe main [ 1:integer <- copy 2:literal 2:integer <- copy 34:literal diff --git a/cpp/019array b/cpp/019array index 3d797161..8a9af8c4 100644 --- a/cpp/019array +++ b/cpp/019array @@ -1,13 +1,14 @@ -// Support for arrays. -:(before "End Mu Types") -// We'll use this array as a running example: +//: Support for arrays. +:(before "End Mu Types Initialization") +//: We'll use this array as a running example: int integer_array = Type_number["integer-array"] = Next_type_number++; Type[integer_array].is_array = true; Type[integer_array].element.push_back(integer); +//: Arrays can be copied around with a single instruction just like integers, +//: no matter how large they are. + :(scenario copy_array) -# Arrays can be copied around with a single instruction just like integers, -# no matter how large they are. recipe main [ 1:integer <- copy 3:literal 2:integer <- copy 14:literal diff --git a/cpp/020call b/cpp/020call index f926a100..8efee27c 100644 --- a/cpp/020call +++ b/cpp/020call @@ -1,4 +1,4 @@ -// So far the recipes we define can't run each other. Let's change that. +//: So far the recipes we define can't run each other. Let's change that. :(scenario "calling_recipe") recipe main [ f @@ -27,6 +27,7 @@ struct routine { calls.push(call(r)); } }; +//: now update routine's helpers :(replace{} "inline size_t& running_at(routine& rr)") inline size_t& running_at(routine& rr) { return rr.calls.top().pc; diff --git a/cpp/021call_ingredient b/cpp/021call_ingredient index f42f0b48..9cc30470 100644 --- a/cpp/021call_ingredient +++ b/cpp/021call_ingredient @@ -1,5 +1,5 @@ -// Calls can take ingredients just like primitives. To access a recipe's -// ingredients, use 'next_ingredient'. +//: Calls can take ingredients just like primitives. To access a recipe's +//: ingredients, use 'next_ingredient'. :(scenario "next_ingredient") recipe main [ f 2:literal diff --git a/cpp/022call_reply b/cpp/022call_reply index 0d0149cb..3deb5464 100644 --- a/cpp/022call_reply +++ b/cpp/022call_reply @@ -1,4 +1,4 @@ -// Calls can also generate results, using 'reply'. +//: Calls can also generate results, using 'reply'. :(scenario "reply") recipe main [ 3:integer, 4:integer <- f 2:literal diff --git a/cpp/makefile b/cpp/makefile index 554c6c45..4fbe6759 100644 --- a/cpp/makefile +++ b/cpp/makefile @@ -5,7 +5,7 @@ mu: makefile tangle/tangle mu.cc # 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 + ./tangle/tangle --until 999 |grep -v "^\s*//:" > mu.cc @make autogenerated_lists >/dev/null tangle/tangle: |