932 - clean up comments in the tangled c++

This commit is contained in:
Kartik K. Agaram 2015-03-16 20:26:59 -07:00
parent b589f25a00
commit 3c435756bc
13 changed files with 223 additions and 201 deletions

View File

@ -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.

View File

@ -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

View File

@ -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>

View File

@ -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(); }

View File

@ -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 [

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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: