Peter Liniker

wrong on the internet for over 20 years

about - home

RPL

The first thing I want is the RPL in REPL. This’ll give me a comfortable context in which evaluation can be done. I’ll want to read stdin one line at a time, unless a filename is given on the command line to read in. To start with, I’ll just echo back everything given as input.

To implement this, I’m going to prefer popular, native-Rust, cross-platform (Linux and Windows) crates wherever possible, rather than implementing this functionality from scratch.

A search of crates.io reveals these most popular crates:

I am also going to stick with stable Rust as far as is possible.

The reality is that this first stage is going to be a lot of copying and pasting from examples from the above mentioned crates. That’s ok, this is going to get harder later.

In general I want to handle errors elegantly, so I’m going to reference the excellent error handling chapter in the future Rust Book to avoid unnecessary panicking.

What I Implemented

I love the conciseness of the load_file() function. I iterated over various error handling patterns until settling on this one. I was delighted to find, later, that this is identical to the final example in the error handling section of the Rust Book!

fn load_file(filename: &str) -> Result<String, io::Error> {
    let mut contents = String::new();

    File::open(filename)?.read_to_string(&mut contents)?;

    Ok(contents)
}

clap-rs

clap provides several methods for declaring command line options. In the end I returned to the first listed method, an explicit builder pattern. There seemed no advantage to the other methods for a single optional filename.

rustyline

It’s not clear that this is going to be the right choice in the long term, but I don’t have enough information about the future yet. I can imagine wanting contextually meaningful multi-line input at some point but I don’t know rustyline well enough to know if I can do this.

For now, all Err(_)s are exit conditions. I’m not distinguishing between actual errors and intentional termination by Ctrl-D, for example. That’s for later.

    loop {
        let readline = reader.readline("# ");

        match readline {
            // valid input
            Ok(line) => {
                reader.add_history_entry(&line);
                println!("{}", line);
            }

            // some kind of termination condition
            Err(e) => {
                if let Some(ref path) = history_file {
                    reader.save_history(&path).unwrap_or_else(|err| {
                        println!("could not save input history in {}: {}", path, err);
                    });
                }

                return Err(e);
            }
        }
    }

Up next…

That’s all for this time around. It didn’t take much time to implement this step, but it was my first time for using either of the third party crates and this kind of application in Rust so I had to reference a reasonable amount of documentation that I have had the luxury of glossing over in the past:

In the next installment I’m expecting to parse the input, converting it to some internal representation, and be able to print that data structure back to stdout.