api Module

In this section we describe the api that is provided that forms the tapestries onto we weave our application.

It is good to keep in mind that we are working toward a Crab renderer. I.e. a turtle graphics capable ferris that responds to our commands.

RenderConfig

#[derive(Debug, PartialEq)]
pub struct RenderConfig {
    pub step: u16,
    pub angle: u16,
}

The RenderConfig is used to configure the Crab renderer. step refers to the distance the Crab will travel when stepping forward. angle refers to the number of degrees the Crab will turn when we ask her to. This is what you'll parse from the l-system file.

RenderInstruction

We define this enum just to give us a simple way to represent what we want our Renderer to do. Our basic definition looks like this:


# #![allow(unused_variables)]
#fn main() {
#[derive(Debug, PartialEq, Eq)]
pub enum RendererInstruction {
    Forward,
    RotateLeft,
    RotateRight,
    Push,
    Pop,
    NoOp,
}
#}

This is a fairly minimal instruction set, but you can still do a lot with it. More full-featured L-system implementations may have more instructions, but we'll start simple for now.

Symbol

The heart of any L-system is Symbols. It is, afterall, a symbol substitution language. Symbol is defined as a trait with a few simple properties. First, it must implement Eq and Hash since we will need to compare them and potentially store them in a hashmap. Secondly, it must implement Copy to facilitate easily producing Symbols based on the rules in our L-system. Lastly, we want it to also implement Display so that we can produce a nicer output when debugging. For this workshop we'll use char, since it already implements all of those traits.

A Symbol must also implement the function fn to_rendering_instruction(&self) -> RendererInstruction;. This function maps each Symbol to an instruction for our renderer to execute. One important bit to point out is that our RendererInstruction enum includes a NoOp instruction, which instructs our Renderer to do nothing. We define this instruction so that it's clear when a Symbol does not map to any particular RendererInstruction. There are way more valid char values than there are RendererInstruction variants, so we need a clear way to represent the case when a Symbol is only used for matching substitutions.

For our workshop, the mapping between char values and rendering instructions will be hard coded as part of the Symbol::to_rendering_instruction function. This mapping follows a fairly common convention, but other implementations will handle this differently. Some implementations even allow you do define this mapping yourself, but we'll leave all that for extra credit.

Rule

The L-system file will contain a rules: section that has one or more rules for symbol replacement. In our API we'll represent each rule as a struct containing the Symbol to match and a Vec<Symbol> of replacements to produce. For example, the rule F => F+F--F+F would be represented by:


# #![allow(unused_variables)]
#fn main() {
Rule {
    match_input: 'F',
    productions: vec!['F', '+', 'F', '-', '-', 'F', '+', 'F'],
}
#}

LSystemRules

Once we have all the Rules parsed, we'll represent the complete set of rules as a HashMap<char, Vec<char>> where the keys are the Symbols to match and the values are the productions for that symbol. This just facilitates easy lookups.

LSystem

This struct is just a holder for the complete set of data that's been parsed from an l-system file. This represents almost everything we need in order to render the final image. The only additional piece we'll need is the number of iterations, but we pass that as a command line argument.