sipp/
lib.rs

1/*!
2Simple parser package.
3
4This package provides a `ByteBuffer` which wraps around a byte stream, and decoders such as
5`Utf8Decoder` which can wrap around a `ByteBuffer`, and a `Parser` which can be wrapped around a
6decoder. The end result is a `Parser` which lets you peek at characters to see what's coming next,
7and then read expected characters. All fallible methods return a `Result` and no method in this
8package should ever panic.
9
10# Examples
11
12## Acornsoft Logo parser
13
14Suppose you want to parse a (simplified) set of Acornsoft Logo instructions, such that you only
15want to accept the "FORWARD", "LEFT", and "RIGHT" instructions, and each instruction must come on
16a line of its own (separated by a newline character), and each instruction is followed by any number
17of space characters, which is then followed by a numeric amount. Example input might look like this:
18
19```text
20FORWARD 10
21RIGHT 45
22FORWARD 20
23RIGHT 10
24FORWARD 5
25LEFT 3
26```
27
28You could use sipp to parse these instructions using code like this:
29
30```
31# use sipp::{parser::Parser, decoder::Utf8Decoder,
32# decoder::ByteStreamCharDecoder, buffer::ByteBuffer};
33# use std::io::{Error, ErrorKind};
34# fn main() -> Result<(), Error> {
35let input = "FORWARD 10\nRIGHT 45\nFORWARD 20\nRIGHT 10\nFORWARD 5\nLEFT 3";
36// We know that Rust strings are UTF-8 encoded, so wrap the input bytes with a Utf8Decoder.
37let decoder = Utf8Decoder::wrap(input.as_bytes());
38// Now wrap the decoder with a Parser to give us useful methods for reading through the input.
39let mut parser = Parser::wrap(decoder);
40# let mut compressed_instructions = String::with_capacity(16);
41// Keep reading while there is still input available.
42while parser.has_more()? {
43    // Read the command by reading everything up to (but not including) the next space.
44    let command = parser.read_up_to(' ')?;
45    # if let Some(command) = command {
46        # match command.as_str() {
47        #     "FORWARD" => compressed_instructions.push('F'),
48        #     "RIGHT" => compressed_instructions.push('R'),
49        #     "LEFT" => compressed_instructions.push('L'),
50        #     _ => {return Err(Error::new(ErrorKind::InvalidData, "Invalid instruction!"));}
51        # }
52    # } else {
53    #     break;
54    # }
55    // Skip past the (one or more) space character.
56    parser.skip_while(|c| c == ' ')?;
57    // Read until the next newline (or the end of input, whichever comes first).
58    let number = parser.read_up_to('\n')?;
59    # compressed_instructions.push_str(number.unwrap().as_str());
60    // Now either there is no further input, or the next character must be a newline.
61    // If the next character is a newline, skip past it.
62    parser.accept('\n')?;
63}
64# assert_eq!(compressed_instructions, "F10R45F20R10F5L3");
65# Ok(())
66# }
67```
68
69## Comma-separated list parser
70
71Given a hardcoded string which represents a comma-separated list, you could use this package to
72parse it like so:
73
74```
75# use sipp::{parser::Parser, decoder::Utf8Decoder,
76# decoder::ByteStreamCharDecoder, buffer::ByteBuffer};
77# fn main() -> Result<(), std::io::Error> {
78let input = "first value,second value,third,fourth,fifth,etc";
79let buffer = ByteBuffer::wrap(input.as_bytes());
80let decoder = Utf8Decoder::wrap_buffer(buffer);
81let mut parser = Parser::wrap(decoder);
82let mut value_list = Vec::new();
83// Keep reading while input is available.
84while parser.has_more()? {
85    // Read up to the next comma, or until the end of input (whichever comes first).
86    // If there is nothing between two commas then just insert an empty string.
87    let value = parser.read_up_to(',')?.unwrap_or("".to_string());
88    value_list.push(value);
89    // Now either there is no further input, or the next character must be a comma.
90    // If the next character is a comma, skip past it.
91    parser.accept(',')?;
92}
93
94assert_eq!(value_list.iter().map(|s| s.to_string()).collect::<Vec<String>>(),
95vec!["first value", "second value", "third", "fourth", "fifth", "etc"]);
96# Ok(())
97# }
98```
99
100# Release notes
101
102## 0.1.0
103Initial release.
104
105## 0.1.1
106* Added `has_more` method to Parser.
107* Adjusted rustdoc based on advice found in
108[Rust API Guidelines](https://rust-lang.github.io/api-guidelines/), primarily in separating out
109error descriptions from the lede and moving them into a dedicated "Errors" section within each
110method's rustdoc comment.
111
112## 0.2.0
113Altered return type of public method `Parser.read_up_to(char)` so that it now returns `None`
114instead of an empty `String`. Adjusted examples and unit tests accordingly.
115
116*/
117
118#![warn(missing_docs)]
119pub mod buffer;
120pub mod decoder;
121pub mod parser;