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