diff options
Diffstat (limited to 'transect')
-rw-r--r-- | transect/000organization.cc | 136 | ||||
-rw-r--r-- | transect/001help.cc | 261 | ||||
-rw-r--r-- | transect/002test.cc | 104 | ||||
-rw-r--r-- | transect/003trace.cc | 408 | ||||
-rw-r--r-- | transect/003trace.test.cc | 124 | ||||
-rw-r--r-- | transect/010vm.cc | 230 | ||||
-rw-r--r-- | transect/011load.cc | 228 |
7 files changed, 0 insertions, 1491 deletions
diff --git a/transect/000organization.cc b/transect/000organization.cc deleted file mode 100644 index 9a1938ff..00000000 --- a/transect/000organization.cc +++ /dev/null @@ -1,136 +0,0 @@ -//: You guessed right: the '000' prefix means you should start reading here. -//: -//: This project is set up 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 contain :(...) directives to insert -//: lines into it. For example: -//: :(after "more events") -//: This directive means: insert the following lines after a line in the -//: program containing the words "more events". -//: -//: A simple tool is included to 'tangle' all the files together in sequence -//: according to their directives into a single source file containing all the -//: code for the project, and then feed the source file to the compiler. -//: (It'll drop these comments starting with a '//:' prefix that only make -//: sense before tangling.) -//: -//: 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. -//: -//: Layers do more than just shuffle code around. In a well-organized codebase -//: it should 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".) Get into the habit of running the included script called -//: 'test_layers' before you commit any changes. -//: -//: This 'subsetting guarantee' ensures that this directory contains a -//: cleaned-up narrative of the evolution of this codebase. Organizing -//: autobiographically allows newcomers to rapidly orient themselves, reading -//: the first few files to understand a simple gestalt of a program's core -//: purpose and features, and later gradually working their way through other -//: features as the need arises. -//: -//: 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 - -// Function prototypes are auto-generated in the 'build*' scripts; define your -// functions in any order. Just be sure to declare each function header all on -// one line, ending with the '{'. Our auto-generation scripts are too minimal -// and simple-minded to handle anything else. -#include "function_list" // by convention, files ending with '_list' are auto-generated - -// Globals -// -// All statements in this section should always define a single variable on a -// single line. The 'build*' scripts will simple-mindedly auto-generate extern -// declarations for them. Remember to define (not just declare) constants with -// extern linkage in this section, since C++ global constants have internal -// linkage by default. -// -// End Globals - -int main(int argc, char* argv[]) { - atexit(reset); - - // End One-time Setup - - // Commandline Parsing - // End Commandline Parsing - - return 0; // End Main -} - -// Unit Tests -// End Unit Tests - -//: our first directive; insert the following header at the start of the program -:(before "End Includes") -#include <stdlib.h> - -//: Without directives or with the :(code) directive, lines get added at the -//: end. -:(code) -void reset() { - // End Reset -} diff --git a/transect/001help.cc b/transect/001help.cc deleted file mode 100644 index 3cab06d9..00000000 --- a/transect/001help.cc +++ /dev/null @@ -1,261 +0,0 @@ -//: Everything this project/binary supports. -//: This should give you a sense for what to look forward to in later layers. - -:(before "End Commandline Parsing") -if (argc <= 1 || is_equal(argv[1], "--help")) { - //: this is the functionality later layers will provide - // currently no automated tests for commandline arg parsing - if (argc <= 1) { - cerr << "Please provide a Mu program to run.\n" - << "\n"; - } - cerr << "Usage:\n" - << " mu [options] [test] [files]\n" - << "or:\n" - << " mu [options] [test] [files] -- [ingredients for function/recipe 'main']\n" - << "Square brackets surround optional arguments.\n" - << "\n" - << "Examples:\n" - << " To load files and run 'main':\n" - << " mu file1.mu file2.mu ...\n" - << " To run 'main' and dump a trace of all operations at the end:\n" - << " mu --trace file1.mu file2.mu ...\n" - << " To run all tests:\n" - << " mu test\n" - << " To load files and then run all tests:\n" - << " mu test file1.mu file2.mu ...\n" - << " To run a single Mu scenario:\n" - << " mu test file1.mu file2.mu ... scenario\n" - << " To run a single Mu scenario and dump a trace at the end:\n" - << " mu --trace test file1.mu file2.mu ... scenario\n" - << " To load files and run only the tests in explicitly loaded files (for apps):\n" - << " mu --test-only-app test file1.mu file2.mu ...\n" - << " To load all files with a numeric prefix in a directory:\n" - << " mu directory1 directory2 ...\n" - << " You can test directories just like files.\n" - << " mu test directory1 directory2 ...\n" - << " To pass ingredients to a mu program, provide them after '--':\n" - << " mu file_or_dir1 file_or_dir2 ... -- ingredient1 ingredient2 ...\n" - << " To see where a mu program is spending its time:\n" - << " mu --profile file_or_dir1 file_or_dir2 ...\n" - << " this slices and dices time spent in various profile.* output files\n" - << "\n" - << " To browse a trace generated by a previous run:\n" - << " mu browse-trace file\n" - ; - return 0; -} - -//: Support for option parsing. -//: Options always begin with '--' and are always the first arguments. An -//: option will never follow a non-option. -:(before "End Commandline Parsing") -char** arg = &argv[1]; -while (argc > 1 && starts_with(*arg, "--")) { - if (false) - ; // no-op branch just so any further additions can consistently always start with 'else' - // End Commandline Options(*arg) - else - cerr << "skipping unknown option " << *arg << '\n'; - --argc; ++argv; ++arg; -} - -//:: Helper function used by the above fragment of code (and later layers too, -//:: who knows?). -//: The :(code) directive appends function definitions to the end of the -//: project. Regardless of where functions are defined, we can call them -//: anywhere we like as long as we format the function header in a specific -//: way: put it all on a single line without indent, end the line with ') {' -//: and no trailing whitespace. As long as functions uniformly start this -//: way, our 'build*' scripts contain a little command to automatically -//: generate declarations for them. -:(code) -bool is_equal(char* s, const char* lit) { - return strncmp(s, lit, strlen(lit)) == 0; -} - -bool starts_with(const string& s, const string& pat) { - string::const_iterator a=s.begin(), b=pat.begin(); - for (/*nada*/; a!=s.end() && b!=pat.end(); ++a, ++b) - if (*a != *b) return false; - return b == pat.end(); -} - -//: I'll throw some style conventions here for want of a better place for them. -//: As a rule I hate style guides. Do what you want, that's my motto. But since -//: we're dealing with C/C++, the one big thing we want to avoid is undefined -//: behavior. If a compiler ever encounters undefined behavior it can make -//: your program do anything it wants. -//: -//: For reference, my checklist of undefined behaviors to watch out for: -//: out-of-bounds access -//: uninitialized variables -//: use after free -//: dereferencing invalid pointers: null, a new of size 0, others -//: -//: casting a large number to a type too small to hold it -//: -//: integer overflow -//: division by zero and other undefined expressions -//: left-shift by negative count -//: shifting values by more than or equal to the number of bits they contain -//: bitwise operations on signed numbers -//: -//: Converting pointers to types of different alignment requirements -//: T* -> void* -> T*: defined -//: T* -> U* -> T*: defined if non-function pointers and alignment requirements are same -//: function pointers may be cast to other function pointers -//: -//: Casting a numeric value into a value that can't be represented by the target type (either directly or via static_cast) -//: -//: To guard against these, some conventions: -//: -//: 0. Initialize all primitive variables in functions and constructors. -//: -//: 1. Minimize use of pointers and pointer arithmetic. Avoid 'new' and -//: 'delete' as far as possible. Rely on STL to perform memory management to -//: avoid use-after-free issues (and memory leaks). -//: -//: 2. Avoid naked arrays to avoid out-of-bounds access. Never use operator[] -//: except with map. Use at() with STL vectors and so on. -//: -//: 3. Valgrind all the things. -//: -//: 4. Avoid unsigned numbers. Not strictly an undefined-behavior issue, but -//: the extra range doesn't matter, and it's one less confusing category of -//: interaction gotchas to worry about. -//: -//: Corollary: don't use the size() method on containers, since it returns an -//: unsigned and that'll cause warnings about mixing signed and unsigned, -//: yadda-yadda. Instead use this macro below to perform an unsafe cast to -//: signed. We'll just give up immediately if a container's ever too large. -//: Basically, Mu is not concerned about this being a little slower than it -//: could be. (https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759de5a7) -//: -//: Addendum to corollary: We're going to uniformly use int everywhere, to -//: indicate that we're oblivious to number size, and since Clang on 32-bit -//: platforms doesn't yet support multiplication over 64-bit integers, and -//: since multiplying two integers seems like a more common situation to end -//: up in than integer overflow. -:(before "End Includes") -#define SIZE(X) (assert((X).size() < (1LL<<(sizeof(int)*8-2))), static_cast<int>((X).size())) - -//: 5. Integer overflow is guarded against at runtime using the -ftrapv flag -//: to the compiler, supported by Clang (GCC version only works sometimes: -//: http://stackoverflow.com/questions/20851061/how-to-make-gcc-ftrapv-work). -:(before "atexit(reset)") -initialize_signal_handlers(); // not always necessary, but doesn't hurt -//? cerr << INT_MAX+1 << '\n'; // test overflow -//? assert(false); // test SIGABRT -:(code) -// based on https://spin.atomicobject.com/2013/01/13/exceptions-stack-traces-c -void initialize_signal_handlers() { - struct sigaction action; - bzero(&action, sizeof(action)); - action.sa_sigaction = dump_and_exit; - sigemptyset(&action.sa_mask); - sigaction(SIGABRT, &action, NULL); // assert() failure or integer overflow on linux (with -ftrapv) - sigaction(SIGILL, &action, NULL); // integer overflow on OS X (with -ftrapv) -} -void dump_and_exit(int sig, siginfo_t* /*unused*/, void* /*unused*/) { - switch (sig) { - case SIGABRT: - #ifndef __APPLE__ - cerr << "SIGABRT: might be an integer overflow if it wasn't an assert() failure or exception\n"; - _Exit(1); - #endif - break; - case SIGILL: - #ifdef __APPLE__ - cerr << "SIGILL: most likely caused by integer overflow\n"; - _Exit(1); - #endif - break; - default: - break; - } -} -:(before "End Includes") -#include <signal.h> - -//: For good measure we'll also enable SIGFPE. -:(before "atexit(reset)") -feenableexcept(FE_OVERFLOW | FE_UNDERFLOW); -//? assert(sizeof(int) == 4 && sizeof(float) == 4); -//? // | exp | mantissa -//? int smallest_subnormal = 0b00000000000000000000000000000001; -//? float smallest_subnormal_f = *reinterpret_cast<float*>(&smallest_subnormal); -//? cerr << "ε: " << smallest_subnormal_f << '\n'; -//? cerr << "ε/2: " << smallest_subnormal_f/2 << " (underflow)\n"; // test SIGFPE -:(before "End Includes") -#include <fenv.h> -:(code) -#ifdef __APPLE__ -// Public domain polyfill for feenableexcept on OS X -// http://www-personal.umich.edu/~williams/archive/computation/fe-handling-example.c -int feenableexcept(unsigned int excepts) { - static fenv_t fenv; - unsigned int new_excepts = excepts & FE_ALL_EXCEPT; - unsigned int old_excepts; - if (fegetenv(&fenv)) return -1; - old_excepts = fenv.__control & FE_ALL_EXCEPT; - fenv.__control &= ~new_excepts; - fenv.__mxcsr &= ~(new_excepts << 7); - return fesetenv(&fenv) ? -1 : old_excepts; -} -#endif - -//: 6. Map's operator[] being non-const is fucking evil. -:(before "Globals") // can't generate prototypes for these -// from http://stackoverflow.com/questions/152643/idiomatic-c-for-reading-from-a-const-map -template<typename T> typename T::mapped_type& get(T& map, typename T::key_type const& key) { - typename T::iterator iter(map.find(key)); - assert(iter != map.end()); - return iter->second; -} -template<typename T> typename T::mapped_type const& get(const T& map, typename T::key_type const& key) { - typename T::const_iterator iter(map.find(key)); - assert(iter != map.end()); - return iter->second; -} -template<typename T> typename T::mapped_type const& put(T& map, typename T::key_type const& key, typename T::mapped_type const& value) { - // map[key] requires mapped_type to have a zero-arg (default) constructor - map.insert(std::make_pair(key, value)).first->second = value; - return value; -} -template<typename T> bool contains_key(T& map, typename T::key_type const& key) { - return map.find(key) != map.end(); -} -template<typename T> typename T::mapped_type& get_or_insert(T& map, typename T::key_type const& key) { - return map[key]; -} -//: The contract: any container that relies on get_or_insert should never call -//: contains_key. - -//: 7. istreams are a royal pain in the arse. You have to be careful about -//: what subclass you try to putback into. You have to watch out for the pesky -//: failbit and badbit. Just avoid eof() and use this helper instead. -:(code) -bool has_data(istream& in) { - return in && !in.eof(); -} - -:(before "End Includes") -#include <assert.h> - -#include <iostream> -using std::istream; -using std::ostream; -using std::iostream; -using std::cin; -using std::cout; -using std::cerr; -#include <iomanip> - -#include <string.h> -#include <string> -using std::string; - -#include <algorithm> -using std::min; -using std::max; diff --git a/transect/002test.cc b/transect/002test.cc deleted file mode 100644 index 817b0d47..00000000 --- a/transect/002test.cc +++ /dev/null @@ -1,104 +0,0 @@ -//: A simple test harness. To create new tests, define functions starting with -//: 'test_'. To run all tests so defined, run: -//: $ ./mu test -//: -//: Every layer should include tests, and can reach into previous layers. -//: However, it seems like a good idea never to reach into tests from previous -//: layers. Every test should be a contract that always passes as originally -//: written, regardless of any later layers. Avoid writing 'temporary' tests -//: that are only meant to work until some layer. - -:(before "End Types") -typedef void (*test_fn)(void); -:(before "Globals") -// move a global ahead into types that we can't generate an extern declaration for -const test_fn Tests[] = { - #include "test_list" // auto-generated; see 'build*' scripts -}; - -:(before "End Globals") -bool Run_tests = false; -bool Passed = true; // set this to false inside any test to indicate failure - -:(before "End Includes") -#define CHECK(X) \ - if (Passed && !(X)) { \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << '\n'; \ - Passed = false; \ - return; /* Currently we stop at the very first failure. */ \ - } - -#define CHECK_EQ(X, Y) \ - if (Passed && (X) != (Y)) { \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << " == " << #Y << '\n'; \ - cerr << " got " << (X) << '\n'; /* BEWARE: multiple eval */ \ - Passed = false; \ - return; /* Currently we stop at the very first failure. */ \ - } - -:(before "End Reset") -Passed = true; - -:(before "End Commandline Parsing") -if (argc > 1 && is_equal(argv[1], "test")) { - Run_tests = true; --argc; ++argv; // shift 'test' out of commandline args -} - -:(before "End Main") -if (Run_tests) { - // Test Runs - // we run some tests and then exit; assume no state need be maintained afterward - - long num_failures = 0; - // End Test Run Initialization - time_t t; time(&t); - cerr << "C tests: " << ctime(&t); - for (size_t i=0; i < sizeof(Tests)/sizeof(Tests[0]); ++i) { -//? cerr << "running " << Test_names[i] << '\n'; - run_test(i); - if (Passed) cerr << '.'; - else ++num_failures; - } - cerr << '\n'; - // End Tests - if (num_failures > 0) { - cerr << num_failures << " failure" - << (num_failures > 1 ? "s" : "") - << '\n'; - return 1; - } - return 0; -} - -:(code) -void run_test(size_t i) { - if (i >= sizeof(Tests)/sizeof(Tests[0])) { - cerr << "no test " << i << '\n'; - return; - } - reset(); - // End Test Setup - (*Tests[i])(); - // End Test Teardown -} - -//: Convenience: run a single test -:(before "Globals") -// Names for each element of the 'Tests' global, respectively. -const string Test_names[] = { - #include "test_name_list" // auto-generated; see 'build*' scripts -}; -:(after "Test Runs") -string maybe_single_test_to_run = argv[argc-1]; -if (!starts_with(maybe_single_test_to_run, "test_")) - maybe_single_test_to_run.insert(0, "test_"); -for (size_t i=0; i < sizeof(Tests)/sizeof(Tests[0]); ++i) { - if (Test_names[i] == maybe_single_test_to_run) { - run_test(i); - if (Passed) cerr << ".\n"; - return 0; - } -} - -:(before "End Includes") -#include <stdlib.h> diff --git a/transect/003trace.cc b/transect/003trace.cc deleted file mode 100644 index 3b4ae596..00000000 --- a/transect/003trace.cc +++ /dev/null @@ -1,408 +0,0 @@ -//: The goal of layers 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. -//: -//: In response, this layer introduces the notion of *domain-driven* testing. -//: We focus on the domain of inputs the whole program needs to handle rather -//: than the correctness of individual functions. All tests invoke the program -//: in a single way: by calling run() with some input. As the program operates -//: on the input, it traces out a list of _facts_ deduced about the domain: -//: trace("label") << "fact 1: " << val; -//: -//: Tests can now check these facts: -//: :(scenario foo) -//: 34 # call run() with this input -//: +label: fact 1: 34 # 'run' should have deduced this fact -//: -label: fact 1: 35 # the trace should not contain such a fact -//: -//: 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, each layer -//: mainly logs facts to the trace with a common *label*. All tests in a layer -//: tend to check facts with this label. Validating the facts logged with a -//: specific label is like calling functions of that layer directly. -//: -//: 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 say 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 hierarchies 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 "End Types") -struct trace_line { - int depth; // optional field just to help browse traces later - string label; - string contents; - trace_line(string l, string c) :depth(0), label(l), contents(c) {} - trace_line(int d, string l, string c) :depth(d), label(l), contents(c) {} -}; - -//: Support for tracing an entire run. -//: Traces can have a lot of overhead, so only turn them on when asked. -:(before "End Commandline Options(*arg)") -else if (is_equal(*arg, "--trace")) { - Save_trace = true; -} -:(before "End Commandline Parsing") -if (Save_trace) { - cerr << "initializing trace\n"; - Trace_stream = new trace_stream; -} -:(code) -void cleanup_main() { - if (!Trace_stream) return; - if (Save_trace) - Trace_stream->save(); - delete Trace_stream; - Trace_stream = NULL; -} -:(before "End One-time Setup") -atexit(cleanup_main); - -:(before "End Types") -// Pre-define some global constants that trace_stream needs to know about. -// Since they're in the Types section, they'll be included in any cleaved -// compilation units. So no extern linkage. -const int Max_depth = 9999; -const int Error_depth = 0; // definitely always print errors - -struct trace_stream { - vector<trace_line> past_lines; - // accumulator for current line - ostringstream* curr_stream; - string curr_label; - int curr_depth; - int collect_depth; - ofstream null_stream; // never opens a file, so writes silently fail - trace_stream() :curr_stream(NULL), curr_depth(Max_depth), collect_depth(Max_depth) {} - ~trace_stream() { if (curr_stream) delete curr_stream; } - - ostream& stream(string label) { - return stream(Max_depth, label); - } - - ostream& stream(int depth, string label) { - if (depth > collect_depth) return null_stream; - curr_stream = new ostringstream; - curr_label = label; - curr_depth = depth; - (*curr_stream) << std::hex; - return *curr_stream; - } - - void save() { - cerr << "saving trace to 'last_run'\n"; - ofstream fout("last_run"); - fout << readable_contents(""); - fout.close(); - } - - // be sure to call this before messing with curr_stream or curr_label - void newline(); - // useful for debugging - string readable_contents(string label); // empty label = show everything -}; - -:(code) -void trace_stream::newline() { - if (!curr_stream) return; - string curr_contents = curr_stream->str(); - if (!curr_contents.empty()) { - past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents)); // preserve indent in contents - if ((!Hide_errors && curr_depth == Error_depth) - || Dump_trace - || (!Dump_label.empty() && curr_label == Dump_label)) - cerr << curr_label << ": " << curr_contents << '\n'; - } - delete curr_stream; - curr_stream = NULL; - curr_label.clear(); - curr_depth = Max_depth; -} - -string trace_stream::readable_contents(string label) { - ostringstream output; - label = trim(label); - for (vector<trace_line>::iterator p = past_lines.begin(); p != past_lines.end(); ++p) - if (label.empty() || label == p->label) - output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n'; - return output.str(); -} - -:(before "End Globals") -trace_stream* Trace_stream = NULL; -int Trace_errors = 0; // used only when Trace_stream is NULL - -:(before "End Globals") -bool Hide_errors = false; // if set, don't print even error trace lines to screen -bool Dump_trace = false; // if set, print trace lines to screen -string Dump_label = ""; // if set, print trace lines matching a single label to screen -:(before "End Reset") -Hide_errors = false; -Dump_trace = false; // toggle this to print traces to screen as they are emitted -Dump_label = ""; - -:(before "End Includes") -#define CLEAR_TRACE delete Trace_stream, Trace_stream = new trace_stream; - -// Top-level helper. IMPORTANT: can't nest -#define trace(...) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__) - -// Just for debugging; 'git log' should never show any calls to 'dbg'. -#define dbg trace(0, "a") -#define DUMP(label) if (Trace_stream) cerr << Trace_stream->readable_contents(label); - -// Errors are a special layer. -#define raise (!Trace_stream ? (++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error")) -// If we aren't yet sure how to deal with some corner case, use assert_for_now -// to indicate that it isn't an inviolable invariant. -#define assert_for_now assert - -// Inside tests, fail any tests that displayed (unexpected) errors. -// Expected errors in tests should always be hidden and silently checked for. -:(before "End Test Teardown") -if (Passed && !Hide_errors && trace_contains_errors()) { - Passed = false; -} -:(code) -bool trace_contains_errors() { - return Trace_errors > 0 || trace_count("error") > 0; -} - -:(before "End Types") -struct end {}; -:(code) -ostream& operator<<(ostream& os, end /*unused*/) { - if (Trace_stream) Trace_stream->newline(); - return os; -} - -:(before "End Globals") -bool Save_trace = false; // if set, write out trace to disk - -// Trace_stream is a resource, lease_tracer uses RAII to manage it. -:(before "End Types") -struct lease_tracer { - lease_tracer(); - ~lease_tracer(); -}; -:(code) -lease_tracer::lease_tracer() { Trace_stream = new trace_stream; } -lease_tracer::~lease_tracer() { - if (Save_trace) Trace_stream->save(); - delete Trace_stream, Trace_stream = NULL; -} -:(before "End Includes") -#define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer; -:(before "End Test Setup") -START_TRACING_UNTIL_END_OF_SCOPE - -:(before "End Includes") -#define CHECK_TRACE_CONTENTS(...) check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) - -#define CHECK_TRACE_CONTAINS_ERRORS() CHECK(trace_contains_errors()) -#define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \ - if (Passed && trace_contains_errors()) { \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \ - DUMP("error"); \ - Passed = false; \ - return; \ - } - -#define CHECK_TRACE_COUNT(label, count) \ - if (Passed && trace_count(label) != (count)) { \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): trace_count of " << label << " should be " << count << '\n'; \ - cerr << " got " << trace_count(label) << '\n'; /* multiple eval */ \ - DUMP(label); \ - Passed = false; \ - return; /* Currently we stop at the very first failure. */ \ - } - -#define CHECK_TRACE_DOESNT_CONTAIN(...) CHECK(trace_doesnt_contain(__VA_ARGS__)) - -:(code) -bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) { - if (!Passed) return false; - if (!Trace_stream) return false; - vector<string> expected_lines = split(expected, ""); - int curr_expected_line = 0; - while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) - ++curr_expected_line; - if (curr_expected_line == SIZE(expected_lines)) return true; - string label, contents; - split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (label != p->label) continue; - if (contents != trim(p->contents)) continue; - ++curr_expected_line; - while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) - ++curr_expected_line; - if (curr_expected_line == SIZE(expected_lines)) return true; - split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); - } - - if (line_exists_anywhere(label, contents)) { - cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): line [" << label << ": " << contents << "] out of order in trace:\n"; - DUMP(""); - } - else { - cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n"; - DUMP(label); - } - Passed = false; - return false; -} - -void split_label_contents(const string& s, string* label, string* contents) { - static const string delim(": "); - size_t pos = s.find(delim); - if (pos == string::npos) { - *label = ""; - *contents = trim(s); - } - else { - *label = trim(s.substr(0, pos)); - *contents = trim(s.substr(pos+SIZE(delim))); - } -} - -bool line_exists_anywhere(const string& label, const string& contents) { - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (label != p->label) continue; - if (contents == trim(p->contents)) return true; - } - return false; -} - -int trace_count(string label) { - return trace_count(label, ""); -} - -int trace_count(string label, string line) { - if (!Trace_stream) return 0; - long result = 0; - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (label == p->label) { - if (line == "" || trim(line) == trim(p->contents)) - ++result; - } - } - return result; -} - -int trace_count_prefix(string label, string prefix) { - if (!Trace_stream) return 0; - long result = 0; - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (label == p->label) { - if (starts_with(trim(p->contents), trim(prefix))) - ++result; - } - } - return result; -} - -bool trace_doesnt_contain(string label, string line) { - return trace_count(label, line) == 0; -} - -bool trace_doesnt_contain(string expected) { - vector<string> tmp = split_first(expected, ": "); - if (SIZE(tmp) == 1) { - raise << expected << ": missing label or contents in trace line\n" << end(); - assert(false); - } - return trace_doesnt_contain(tmp.at(0), tmp.at(1)); -} - -vector<string> split(string s, string delim) { - vector<string> result; - size_t begin=0, end=s.find(delim); - while (true) { - if (end == string::npos) { - result.push_back(string(s, begin, string::npos)); - break; - } - result.push_back(string(s, begin, end-begin)); - begin = end+SIZE(delim); - end = s.find(delim, begin); - } - return result; -} - -vector<string> split_first(string s, string delim) { - vector<string> result; - size_t end=s.find(delim); - result.push_back(string(s, 0, end)); - if (end != string::npos) - result.push_back(string(s, end+SIZE(delim), string::npos)); - return result; -} - -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); -} - -:(before "End Includes") -#include <vector> -using std::vector; -#include <list> -using std::list; -#include <set> -using std::set; - -#include <sstream> -using std::istringstream; -using std::ostringstream; - -#include <fstream> -using std::ifstream; -using std::ofstream; diff --git a/transect/003trace.test.cc b/transect/003trace.test.cc deleted file mode 100644 index 67b4c345..00000000 --- a/transect/003trace.test.cc +++ /dev/null @@ -1,124 +0,0 @@ -void test_trace_check_compares() { - trace("test layer") << "foo" << end(); - CHECK_TRACE_CONTENTS("test layer: foo"); -} - -void test_trace_check_ignores_other_layers() { - trace("test layer 1") << "foo" << end(); - trace("test layer 2") << "bar" << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); - CHECK_TRACE_DOESNT_CONTAIN("test layer 2: foo"); -} - -void test_trace_check_ignores_leading_whitespace() { - trace("test layer 1") << " foo" << end(); - CHECK_EQ(trace_count("test layer 1", /*too little whitespace*/"foo"), 1); - CHECK_EQ(trace_count("test layer 1", /*too much whitespace*/" foo"), 1); -} - -void test_trace_check_ignores_other_lines() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "bar" << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); -} - -void test_trace_check_ignores_other_lines2() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "bar" << end(); - CHECK_TRACE_CONTENTS("test layer 1: bar"); -} - -void test_trace_ignores_trailing_whitespace() { - trace("test layer 1") << "foo\n" << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); -} - -void test_trace_ignores_trailing_whitespace2() { - trace("test layer 1") << "foo " << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); -} - -void test_trace_orders_across_layers() { - trace("test layer 1") << "foo" << end(); - trace("test layer 2") << "bar" << end(); - trace("test layer 1") << "qux" << end(); - CHECK_TRACE_CONTENTS("test layer 1: footest layer 2: bartest layer 1: qux"); -} - -void test_trace_supports_count() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "foo" << end(); - CHECK_EQ(trace_count("test layer 1", "foo"), 2); -} - -void test_trace_supports_count2() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "bar" << end(); - CHECK_EQ(trace_count("test layer 1"), 2); -} - -void test_trace_count_ignores_trailing_whitespace() { - trace("test layer 1") << "foo\n" << end(); - CHECK_EQ(trace_count("test layer 1", "foo"), 1); -} - -// 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_errors is set. -// pending: raise doesn't have to be saved if Hide_errors 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_errors is set. - -// can't check trace because trace methods call 'split' - -void test_split_returns_at_least_one_elem() { - vector<string> result = split("", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result.at(0), ""); -} - -void test_split_returns_entire_input_when_no_delim() { - vector<string> result = split("abc", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result.at(0), "abc"); -} - -void test_split_works() { - vector<string> result = split("abc,def", ","); - CHECK_EQ(result.size(), 2); - CHECK_EQ(result.at(0), "abc"); - CHECK_EQ(result.at(1), "def"); -} - -void test_split_works2() { - vector<string> result = split("abc,def,ghi", ","); - CHECK_EQ(result.size(), 3); - CHECK_EQ(result.at(0), "abc"); - CHECK_EQ(result.at(1), "def"); - CHECK_EQ(result.at(2), "ghi"); -} - -void test_split_handles_multichar_delim() { - vector<string> result = split("abc,,def,,ghi", ",,"); - CHECK_EQ(result.size(), 3); - CHECK_EQ(result.at(0), "abc"); - CHECK_EQ(result.at(1), "def"); - CHECK_EQ(result.at(2), "ghi"); -} - -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"); -} diff --git a/transect/010vm.cc b/transect/010vm.cc deleted file mode 100644 index fad1ee77..00000000 --- a/transect/010vm.cc +++ /dev/null @@ -1,230 +0,0 @@ -//: type definitions start with either 'record' or 'choice' - -:(before "End Types") -typedef int type_id; -:(before "End Globals") -map</*name*/string, type_id> Type_id; -map<type_id, type_info> Type_info; -type_id Next_type_id = 1; -// primitive types -type_id Literal_type_id = 0, Int_type_id = 0, Byte_type_id = 0, Address_type_id = 0, Array_type_id = 0, Ref_type_id = 0; -:(before "End Types") -struct type_info { - type_id id; - string name; - kind_of_type kind; - int size; // in bytes - vector<type_declaration> elements; - type_info() :kind(PRIMITIVE), size(0) {} -}; -:(before "struct type_info") -enum kind_of_type { - PRIMITIVE, - RECORD, - CHOICE, -}; -struct type_declaration { - string name; - vector<type_id> type; -}; - -//: global definitions start with 'var' - -:(before "End Types") -typedef int global_id; -:(before "End Globals") -map</*name*/string, global_id> Global_id; -map<global_id, global_info> Global_info; -global_id Next_global_id = 1; -:(before "End Types") -struct global_info { - global_id id; - vector<type_id> type; - int address; - global_info() :address(0) {} -}; - -//: function definitions start with 'fn' - -:(before "End Types") -typedef int function_id; -:(before "End Globals") -map</*name*/string, function_id> Function_id; -map<function_id, function_info> Function_info; -function_id Next_function_id = 1; -:(before "End Types") -struct function_info { - function_id id; - string name; - vector<operand> in; - vector<operand> in_out; - vector<instruction> instructions; - map</*local variable name*/string, int> stack_offset; - function_info() :id(0) {} -}; -:(before "struct function_info") -// operands have form name/property1/property2/... : (type1 type2 ...) -struct operand { - string name; - vector<type_id> type; - vector<string> properties; - operand(string); - void set_type(istream&); -}; - -struct instruction { - function_id id; - string name; - vector<operand> in; - vector<operand> in_out; -}; -:(code) -operand::operand(string s) { - istringstream in(s); - name = slurp_until(in, '/'); - while (has_data(in)) - properties.push_back(slurp_until(in, '/')); -} - -// extremely hacky; assumes a single-level list of words in parens, with no nesting -void operand::set_type(istream& in) { - assert(has_data(in)); - string curr; - in >> curr; -//? cerr << "2: " << curr << '\n'; - if (curr.at(0) != '(') { - type.push_back(get(Type_id, curr)); - return; - } - curr = curr.substr(/*skip '('*/1); - while (!ends_with(curr, ")")) { - if (curr.empty()) continue; - assert(curr.at(0) != '('); - type.push_back(get(Type_id, curr)); - // update - assert(has_data(in)); - in >> curr; - } - assert(ends_with(curr, ")")); - curr = curr.substr(0, SIZE(curr)-1); - if (!curr.empty()) { - /*'(' or ')' isn't a token by itself*/ - type.push_back(get(Type_id, curr)); - } -} - -string to_string(const operand& o) { - ostringstream out; - out << o.name; - if (o.type.empty()) return out.str(); - out << " : "; - if (SIZE(o.type) == 1) { - out << get(Type_info, o.type.at(0)).name; - return out.str(); - } - out << "("; - for (int i = 0; i < SIZE(o.type); ++i) { - if (i > 0) out << ", "; - out << get(Type_info, o.type.at(i)).name; - } - out << ")"; - return out.str(); -} - -string slurp_until(istream& in, char delim) { - ostringstream out; - char c; - while (in >> c) { - if (c == delim) { - // drop the delim - break; - } - out << c; - } - return out.str(); -} - -bool ends_with(const string& s, const string& pat) { - for (string::const_reverse_iterator p = s.rbegin(), q = pat.rbegin(); q != pat.rend(); ++p, ++q) { - if (p == s.rend()) return false; // pat too long - if (*p != *q) return false; - } - return true; -} - -:(before "End One-time Setup") -init_primitive_types(); -:(code) -void init_primitive_types() { - Literal_type_id = new_type("literal", PRIMITIVE, 0); - Int_type_id = new_type("int", PRIMITIVE, 4); - Byte_type_id = new_type("byte", PRIMITIVE, 1); - Address_type_id = new_type("address", PRIMITIVE, 4); - Array_type_id = new_type("array", PRIMITIVE, 0); // size will depend on length - Ref_type_id = new_type("ref", PRIMITIVE, 8); // address + alloc id -} - -type_id new_type(string name, kind_of_type kind, int size) { - assert(!contains_key(Type_id, name)); - int result = Next_type_id++; - put(Type_id, name, result); - assert(!contains_key(Type_info, result)); - type_info& curr = Type_info[result]; // insert - curr.id = result; - curr.name = name; - curr.kind = kind; - curr.size = size; - return result; -} - -//: Start each test by undoing the previous test's types, globals and functions - -:(before "End One-time Setup") -save_snapshots(); -:(before "End Reset") -restore_snapshots(); - -:(before "End Globals") -map<string, type_id> Type_id_snapshot; -map<type_id, type_info> Type_info_snapshot; -type_id Next_type_id_snapshot = 0; - -map<string, global_id> Global_id_snapshot; -map<global_id, global_info> Global_info_snapshot; -global_id Next_global_id_snapshot = 0; - -map<string, function_id> Function_id_snapshot; -map<function_id, function_info> Function_info_snapshot; -function_id Next_function_id_snapshot = 0; -:(code) -void save_snapshots() { - Type_id_snapshot = Type_id; - Type_info_snapshot = Type_info; - Next_type_id_snapshot = Next_type_id; - - Global_id_snapshot = Global_id; - Global_info_snapshot = Global_info; - Next_global_id_snapshot = Next_global_id; - - Function_id_snapshot = Function_id; - Function_info_snapshot = Function_info; - Next_function_id_snapshot = Next_function_id; -} - -void restore_snapshots() { - Type_id = Type_id_snapshot; - Type_info = Type_info_snapshot; - Next_type_id = Next_type_id_snapshot; - - Global_id = Global_id_snapshot; - Global_info = Global_info_snapshot; - Next_global_id = Next_global_id_snapshot; - - Function_id = Function_id_snapshot; - Function_info = Function_info_snapshot; - Next_function_id = Next_function_id_snapshot; -} - -:(before "End Includes") -#include <map> -using std::map; diff --git a/transect/011load.cc b/transect/011load.cc deleted file mode 100644 index f8cf96e8..00000000 --- a/transect/011load.cc +++ /dev/null @@ -1,228 +0,0 @@ -//: Phase 1 of translating Mu code: load it from a textual representation. -//: -//: The process of translating Mu code: -//: load -> check types -> convert - -:(scenarios load) // use 'load' instead of 'run' in all scenarios in this layer -:(scenario single_function) -fn foo [ - 1 : int <- copy 23 -] -+parse: function: foo -+parse: 0 in operands -+parse: 0 in_out operands -+parse: instruction: copy -+parse: in => 23 : literal -+parse: in_out => 1 : int - -:(code) -void load(string form) { - istringstream in(form); - load(in); -} - -void load(istream& in) { - while (has_data(in)) { - string line_data; - getline(in, line_data); - if (line_data.empty()) continue; // maybe eof - char c = first_non_whitespace(line_data); - if (c == '\0') continue; // only whitespace - if (c == '#') continue; // only comment - trace(99, "parse") << "line: " << line_data << end(); - istringstream lin(line_data); - while (has_data(lin)) { - string word_data; - lin >> word_data; - if (word_data.empty()) continue; // maybe eof - if (word_data[0] == '#') break; // comment; ignore rest of line - if (word_data == "record") - load_record(lin, in); - else if (word_data == "choice") - load_choice(lin, in); - else if (word_data == "var") - load_global(lin, in); - else if (word_data == "fn") - load_function(lin, in); - else - raise << "unrecognized top-level keyword '" << word_data << "'; should be one of 'record', 'choice', 'var' or 'fn'\n" << end(); - break; - } - // nothing here, because we'll be at the next top-level declaration - } -} - -void load_record(istream& first_line, istream& in) { -} - -void load_choice(istream& first_line, istream& in) { -} - -void load_global(istream& first_line, istream& in) { -} - -void load_function(istream& first_line, istream& in) { - string name; - assert(has_data(first_line)); - first_line >> name; - trace(99, "parse") << "function: " << name << end(); - function_info& curr = new_function(name); - string tmp; - // read in parameters - while (has_data(first_line)) { - // read operand name - first_line >> tmp; -//? cerr << "0: " << tmp << '\n'; - if (tmp == "[") break; - if (tmp == "->") break; - assert(tmp != ":"); - curr.in.push_back(operand(tmp)); - - // skip ':' - assert(has_data(first_line)); - first_line >> tmp; -//? cerr << "1: " << tmp << '\n'; - assert(tmp == ":"); // types are required in function headers - - // read operand type - assert(has_data(first_line)); - curr.in.back().set_type(first_line); - } - // read in-out parameters - while (tmp != "[" && has_data(first_line)) { - // read operand name - first_line >> tmp; -//? cerr << "inout 0: " << tmp << '\n'; - if (tmp == "[") break; - assert(tmp != "->"); - assert(tmp != ":"); // types are required in function headers - curr.in_out.push_back(operand(tmp)); - - // skip ':' - assert(has_data(first_line)); - first_line >> tmp; -//? cerr << "inout 1: " << tmp << '\n'; - assert(tmp == ":"); - - // read operand type - assert(has_data(first_line)); - curr.in.back().set_type(first_line); - } - trace(99, "parse") << " " << SIZE(curr.in) << " in operands" << end(); - trace(99, "parse") << " " << SIZE(curr.in_out) << " in_out operands" << end(); - // not bothering checking for tokens past '[' in first_line - - // read instructions - while (has_data(in)) { - string line_data; - getline(in, line_data); - if (first_non_whitespace(line_data) == ']') break; -//? bool has_in_out = (line_data.find("<-") != string::npos); - istringstream line(line_data); - vector<string> words; - bool has_in_out = false; - while (has_data(line)) { - string w; - line >> w; - words.push_back(w); - if (w == "<-") - has_in_out = true; - } - instruction inst; - int i = 0; - assert(i < SIZE(words)); - if (has_in_out) { - while (i < SIZE(words)) { -//? cerr << "in-out operand: " << i << ' ' << words.at(i) << '\n'; - inst.in_out.push_back(operand(words.at(i))); - ++i; - assert(i < SIZE(words)); - if (words.at(i) == ":") { - ++i; // skip ':' - assert(i < SIZE(words)); - assert(words.at(i) != "<-"); - assert(words.at(i) != ":"); - istringstream tmp(words.at(i)); -//? cerr << "setting type to " << i << ' ' << words.at(i) << '\n'; - inst.in_out.back().set_type(tmp); -//? cerr << "done\n"; - ++i; - assert(i < SIZE(words)); - } - if (words.at(i) == "<-") break; - } - assert(i < SIZE(words)); - assert(words.at(i) == "<-"); - ++i; - } - assert(i < SIZE(words)); - assert(words.at(i) != "<-"); - assert(words.at(i) != ":"); - inst.name = words.at(i); - ++i; - while (i < SIZE(words)) { - inst.in.push_back(operand(words.at(i))); - ++i; - if (i < SIZE(words) && words.at(i) == ":") { - ++i; // skip ':' - assert(i < SIZE(words)); - assert(words.at(i) != "<-"); - assert(words.at(i) != ":"); - istringstream tmp(words.at(i)); - inst.in.back().set_type(tmp); - ++i; - } - else if (is_integer(inst.in.back().name)) { - inst.in.back().type.push_back(Literal_type_id); - } - } - trace(99, "parse") << "instruction: " << inst.name << end(); - for (int i = 0; i < SIZE(inst.in); ++i) - trace(99, "parse") << " in => " << to_string(inst.in.at(i)) << end(); - for (int i = 0; i < SIZE(inst.in_out); ++i) - trace(99, "parse") << " in_out => " << to_string(inst.in_out.at(i)) << end(); - curr.instructions.push_back(inst); - } -} - -function_info& new_function(string name) { - assert(!contains_key(Function_id, name)); - int id = Next_function_id++; - put(Function_id, name, id); - assert(!contains_key(Function_info, id)); - function_info& result = Function_info[id]; // insert - result.id = id; - result.name = name; - return result; -} - -char first_non_whitespace(string in) { - for (int i = 0; i < SIZE(in); ++i) - if (!isspace(in.at(i))) return in.at(i); - return '\0'; -} - -bool is_integer(const string& s) { - return s.find_first_not_of("0123456789-") == string::npos // no other characters - && s.find_first_of("0123456789") != string::npos // at least one digit - && s.find('-', 1) == string::npos; // '-' only at first position -} - -int to_integer(string n) { - char* end = NULL; - // safe because string.c_str() is guaranteed to be null-terminated - int result = strtoll(n.c_str(), &end, /*any base*/0); - if (*end != '\0') cerr << "tried to convert " << n << " to number\n"; - assert(*end == '\0'); - return result; -} - -void test_is_integer() { - CHECK(is_integer("1234")); - CHECK(is_integer("-1")); - CHECK(!is_integer("234.0")); - CHECK(is_integer("-567")); - CHECK(!is_integer("89-0")); - CHECK(!is_integer("-")); - CHECK(!is_integer("1e3")); // not supported -} |