# Session 3 homework I expect that fully engaging with this homework may take more than an hour, since it'll involve bringing together a lot of the skills you've learned so far, and also writing docs. Indeed I'm going to suggest you spread this homework over the two weeks, by first reading through everything and experimenting on your own a bit in an unrelated repository, and then we can have a recap session next week. After that, you'll complete the work in this homework description, ready for continuing and expanding on it during the subsequent weeks. Do **NOT** use the above as a reason to put off starting your homework though. You should start it ASAP and _talk_ to me if you need to. # Introduction This week we're reducing the complexity of your homework load because there are a number of moving parts. From this week onward, your homework will be building on the same codebase. As such I recommend that you create a git repository to store your homework in. Wherever I remind you to commit in this code, also take that as instruction to push it somewhere too. We are, unsurprisingly, going to be developing a Brainfuck related project. In order to put into practice, some of the stuff we looked at in session three we're going to be constructing a slightly contrived project structure. This is not entirely mad, but also probably overkill for the amount of code we'll be writing in this project. Still, homework is sometimes meant to be contrived. # Setup steps 1. Create a new binary crate, called 'bft'. This will be the top level project in your repository, so once you've created it, you may as well commit it straight away (even though it's essentially hello world) 2. Inside that, create a new library crate called 'bft_types' and alongside it one called 'bft_interp' 3. Add those two projects as workspaced projects to the Cargo.toml of the 'bft' project itself and add them as dependencies of 'bft' 4. Add 'bft_types' as a dependency of 'bft_interp' 5. Commit all that. If you are unsure at this point, ping me on IRC or wherever and ask me to look at your repository to confirm you're on the right track. # Confirmation At this point, if you `cargo build` in the top level, you should have the two libraries built (types first, then interp) and then the binary crate. If you run `cargo test --all` then the unit tests in the default libraries should run, as well as zero doctests and zero integration tests. Finally, if you run `cargo doc --all` and then `cargo doc --open` then your browser should open on the documentation for your 'bft' crate but on the left should be the 'bft_types' and 'bft_interp' crates At this point you can go into the library crates and delete the `it_works` test code since that's not useful to us. Commit that and we're ready to begin the real work. # Task One - useful types We are going to put some useful types into 'bft_types' which we'll use throughout our work. The first thing I'd like is for us to have a representation of a BF program. This will likely be a struct with the filename it was loaded from, and a Vec<> of marked instructions similar to that developed in part two of week two's homework. Please annotate any structs or enums you write for this with the Debug trait since we'll likely need that in the future. Add to this module an implementation block for your program struct and within it write a function which takes any path-like input (`AsRef`) and returns a result of loading and parsing it into the vector of instructions. Such a function should likely be called something like `from_file`. Since BF programs define meaning for all bytes (anything not one of the eight instruction characters is a comment) you can use `std::io::Result` as the return type quite safely. Ensure that your code _CANNOT_ panic here. i.e. propagate errors upward cleanly. For extra credit, separate the file-access from the construction of the program structure by also writing a `new` function which takes a filename (path-like as before) and the content as a string-like. You can call that `new` because it shouldn't be able to fail and so you can return your structure directly. Make your `from_file` call this to construct the actual result. Your structure itself (and any types which you end up needing to allow users of the library to interact with) will need to be public, _but_ your structure's members should be private, so you might want to add accessor methods for the data within. Ideally do not expose the `Vec<_>` nature of the container for your parsed instructions. Instead return a borrow of the slice underlying the vector if you can. # Task Two - interpreting a program Our interpreter will need a structure too. BF programs run within a very simple "virtual machine" which consists of a tape (an array of numbers) and a head (a pointer to somewhere in that array). The tape can be of varying lengths, and the numbers can be of varying sizes. Classical BF has byte-sized numbers and 30,000 of them in the array. In the 'bft_interp' crate, create a structure to represent the virtual machine. Its type should be parameterised over the type of the number kind for the 'cell' and the associated 'new' function for the struct should take the number of cells to allocate for the tape. Ideally it should take that as an usize and if zero is provided, it should use the default 30,000. Also, you should allow your interpreter to grow the tape optionally, by passing in a boolean to indicate if the tape should be permitted to grow; clearly this will also need a boolean stored in your interpreter struct. # Task Three - Bringing things together a little In the 'bft' crate at the top level, your main function should be set to return a `Result<(), Box>`. Eventually we'll likely undo this but for now, it's a convenient way to get our errors propagated out and reported. You may, if you want, set up a type alias for this to make it shorter to write. Retrieve the first argument on the command line, and using the 'bft_types' crate, load that as a BF program into a variable in main. Now, using the 'bft_interp' crate, construct a virtual machine which is like the classic BF machine - a byte based (u8) 30,000 cell tape, which does not extend automatically. # Task Four - An "interpreter" We need to be able to interpret our code. For now, we'll write an interpreter function which just prints the program out just like we did for our previous homework. Add a function to the implementation of your VM structure in 'bft_interp' which takes a borrow of a program structure (as per 'bft_types') and prints it out like we did in part two of the previous homework. For now, this function doesn't need to return anything since it can't fail and doesn't do any useful work. Wire the use of this function into `main` in the 'bft' crate and verify that you can run your program with a BF program as input, and get the correct output. Commit this if you've not already # Task Five - Some tests For now, we're going to add tests only to the 'bft_types' crate. We want some tests which will confirm that we correctly parse the eight BF instructions and that we correctly track the line and column numbers. Write these as test functions inside the crate's lib.rs for now, in a sub-module `test` which is guarded like the default example test was. Verify that running `cargo test --all` or `cargo test -p bft_types` runs all your tests and that they pass. Once again, make a commit if you've not already # Task Six - Documentation If you've re-run `cargo doc --all` and re-explored your generated docs, you'll notice that you have some bare struct pages etc now. Rust has done its best but you need to write some documentation around this. Add a documentation comment for the 'bft_types' crate itself, and document the struct(s) and enum(s) in there as best you can. Remember to document every public function too, and though you don't need to, you should feel free to document the private members and functions as well, should you wish. If you can think of ways to do so, safely, write examples into your documentation in a way in which they can be tested. For the `from_file` function you likely will need to annotate that test so that it doesn't get run since it won't have access to the filesystem in a useful fashion. You can do that by adding the `no_run` attribute to the code block. Once you're satisfied with your doc comments, run `cargo doc --all` and reload your documentation in a browser to check it over. Correct any issues, repeat, until you think the docs of your types library are as good as they could be. Commit this, if you've not already # Task Seven By now, you're feeling excited I hope. So please also add documentation to the 'bft_interp' crate, obviously it won't be massively complete right now, but you can document the virtual machine structure, the meaning of the parameter type, and the `new` function, with some utility. I wouldn't bother documenting the fake interpreter function yet. Also, if you add documentation to the 'bft' crate, you'll notice that even though it's a binary, the documentation does get built, so add some rudimentary docs to there if you want. Remember that once you've made your changes, ensure they're committed. # Extra Credit (though frankly you really ought to do this) Set up CI on whichever git server you've picked (e.g. Travis or Github Actions for Github, or gitlab-ci for GitLab). Ensure that your code passes its tests, and is formatted properly, on every commit. If you're feeling super-duper excited, make it pass clippy too.