Compare commits

...

10 commits

7 changed files with 156 additions and 79 deletions

2
Cargo.lock generated
View file

@ -4,4 +4,4 @@ version = 3
[[package]] [[package]]
name = "eyes" name = "eyes"
version = "1.1.1" version = "1.3.0"

View file

@ -1,14 +1,13 @@
[package] [package]
name = "eyes" name = "eyes"
version = "1.1.1" version = "1.3.0"
edition = "2018" edition = "2018"
description = "A simpler way to parse using human-readable templates" description = "Parse and convert strings using human-readable templates."
license="BSD-3-Clause" license="BSD-3-Clause"
homepage = "https://git.palmoe.dk/vodofrede/eyes/" homepage = "https://git.palmoe.dk/vodofrede/eyes/"
repository = "https://git.palmoe.dk/vodofrede/eyes/" repository = "https://git.palmoe.dk/vodofrede/eyes/"
readme = "README.md" readme = "README.md"
keywords = ["parsing", "parse", "simple", "no-deps"] keywords = ["parsing", "parse", "simple", "no-deps"]
categories = ["encoding", "parsing", "text-processing"] categories = ["encoding", "parsing", "text-processing"]
exclude = [".gitignore", "src/main.rs"]
[dependencies] [dependencies]

View file

@ -1,53 +1,54 @@
# Eyes # eyes
## A simpler way to parse using human-readable templates Parse and convert strings using human-readable templates.
Eyes was made for the primary purpose of parsing challenge inputs for [Advent of Code](https://adventofcode.com) challenges. The crate's primary purpose is parsing challenge inputs for [Advent of Code](https://adventofcode.com) challenges. It currently provides limited functionality, but more options may be added provided they are useful additions for parsing slightly more complicated formats.
It currently provides limited functionality, but more options may be added provided they are useful additions for parsing slightly more complicated formats. This crate does not have any dependencies, as I wanted to keep it simple to and lightweight in design.
Eyes does not have any dependencies, as I wanted to keep it simple to and lightweight in design. Good performance is not guaranteed, as the library isn't well tested yet. ## Syntax
I was told this functionality is similar to `scanf` from C. The only special characters in templates are curly brackets `{}`. These act as stand-ins for where the extracted values are in the input strings.
### Examples: ## Examples:
```rust ```rust
use eyes::parse;
let input = "#lol @ 338,7643: 20.2x24.5"; let input = "#lol @ 338,7643: 20.2x24.5";
let template = "#{} @ {},{}: {}x{}"; let template = "#{} @ {},{}: {}x{}";
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let (id, x, y, w, h) = parse!(input, template, String, isize, isize, f64, f64); let (id, x, y, w, h) = parse!(input, template, String, isize, isize, f64, f64);
println!("id: {:?}", id);
println!("x: {:?}", x);
println!("y: {:?}", y);
println!("w: {:?}", w);
println!("h: {:?}", h);
assert_eq!((id.as_str(), x, y, w, h), ("lol", 338, 7643, 20.2, 24.5)); assert_eq!((id.as_str(), x, y, w, h), ("lol", 338, 7643, 20.2, 24.5));
``` ```
**Eyes** will try to expand its captures, so that the following example also works as expected: **eyes** will match capture groups greedily and expand them as far as possible, so that the following example also works as expected:
```rust ```rust
use eyes::parse;
let input = "turn off 660,55 through 986,197"; let input = "turn off 660,55 through 986,197";
let template = "{} {},{} through {},{}"; let template = "{} {},{} through {},{}";
let (op, x1, y1, x2, y2) = parse!(input, template, String, usize, usize, usize, usize);
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let (op, x1, y1, x2, y2) = try_parse!(input, template, String, usize, usize, usize, usize);
println!("op: {:?}", op);
println!("p1: {:?}", (&x1, &y1));
println!("p2: {:?}", (&x2, &y2));
assert_eq!( assert_eq!(
(op.unwrap().as_str(), x1, y1, x2, y2), (op.as_str(), x1, y1, x2, y2),
("turn off", Ok(660), Ok(55), Ok(986), Ok(197)) ("turn off", 660, 55, 986, 197)
); );
``` ```
Notice that "turn off" is captured correctly, even though it contains a space.
For error handling, the `try_parse` macro is provided which can be very useful in parsing potentially malformed input:
```rust
use eyes::try_parse;
let input = "1 2\n3,4\n5 6";
let result = input
.lines()
.filter_map(|line| try_parse!(line, "{} {}", i64, i64))
.collect::<Vec<_>>();
assert_eq!(vec![(1, 2), (5, 6)], result);
```

12
src/bin/sandbox.rs Normal file
View file

@ -0,0 +1,12 @@
use eyes::parse;
fn main() {
if let Some((a, b, c)) = eyes::try_parse!("1 2,3", "{} {},{}", u8, u8, u8) {
assert!(a == 1 && b == 2 && c == 3);
} else {
unreachable!("This should not happen, as the pattern is matchable to the input");
}
let input = "1,2, 3";
parse!(input, "{},{}, {}", i64, i64, i64);
}

View file

@ -1,8 +1,28 @@
pub struct Parser<'a> { #![allow(clippy::needless_question_mark)]
#![warn(clippy::all, clippy::cargo)]
#![deny(missing_docs, unsafe_code)]
#![doc = include_str!("../README.md")]
/// A list of captures, created by calling [`Captures::new()`] with the input and template strings.
///
/// An easier way to use this struct is with the [`parse`] and [`try_parse`] macros, which allow for automatic type conversion of captures.
pub struct Captures<'a> {
captures: Vec<&'a str>, captures: Vec<&'a str>,
} }
impl<'a> Parser<'a> { impl<'a> Captures<'a> {
/// Create a new list of captures from input and template strings.
///
/// The input and template strings must live as long as the list of captures itself, as the captures list borrows from them.
///
/// ## Examples
///
/// Basic usage:
/// ```
/// # use eyes::Captures;
/// let captures = Captures::new("haystack|needle|haystack", "haystack|{}|haystack");
/// assert_eq!(captures.unwrap().to_inner()[0], "needle");
/// ```
pub fn new(input: &'a str, template: &'a str) -> Option<Self> { pub fn new(input: &'a str, template: &'a str) -> Option<Self> {
// find all patterns in the template // find all patterns in the template
let patterns = template let patterns = template
@ -25,8 +45,6 @@ impl<'a> Parser<'a> {
right = right.trim_start_matches(|c: char| c.is_whitespace()); right = right.trim_start_matches(|c: char| c.is_whitespace());
} }
println!("left: '{}', right: '{}'", left, right);
// if the right side of the split doesn't contain the pattern, // if the right side of the split doesn't contain the pattern,
// we don't have to check if we can expand the match // we don't have to check if we can expand the match
if right.contains(pat) { if right.contains(pat) {
@ -38,6 +56,7 @@ impl<'a> Parser<'a> {
+ left.len(); + left.len();
while next_pattern_index > pattern_index { while next_pattern_index > pattern_index {
// we split two times, so we don't get the pattern in any of the splits
let (left_side, _) = input.split_at(pattern_index + 1); let (left_side, _) = input.split_at(pattern_index + 1);
left = left_side; left = left_side;
let (_, right_side) = input.split_at(pattern_index + 1 + pat.len()); let (_, right_side) = input.split_at(pattern_index + 1 + pat.len());
@ -57,38 +76,69 @@ impl<'a> Parser<'a> {
Some(Self { captures }) Some(Self { captures })
} }
pub fn captures(&self) -> Vec<&'a str> { /// Get the internal representation of the captures as an owned value, which allow usage of standard [`Vec`] methods.
pub fn to_inner(&self) -> Vec<&'a str> {
self.captures.to_owned() self.captures.to_owned()
} }
/// Get the internal representation of the captures as a reference, which allow usage of standard [`Vec`] methods.
pub fn as_inner(&self) -> &Vec<&'a str> {
&self.captures
}
} }
#[macro_export] /// Parse an input and template, and convert the captures to the specified types.
macro_rules! parse { /// This version returns an option, indicating whether the input matched the template by returning None in the negative case.
($input: expr, $pattern: tt, $($type:ty),*) => { ///
{ /// # Examples
let parser = $crate::Parser::new($input, $pattern).unwrap(); ///
let captures = parser.captures(); /// Basic usage:
let mut iter = captures.iter(); /// ```
/// # #[macro_use] extern crate eyes;
($(iter.next().unwrap().parse::<$type>().unwrap()),*) /// if let Some((a, b, c)) = eyes::try_parse!("1 2,3", "{} {},{}", u8, u8, u8) {
} /// assert!(a == 1 && b == 2 && c == 3);
}; /// } else {
} /// unreachable!("This should not happen, as the pattern is matchable to the input");
/// }
/// ```
#[macro_export] #[macro_export]
macro_rules! try_parse { macro_rules! try_parse {
($input: expr, $pattern: tt, $($type:ty),*) => { ($input: expr, $pattern: tt, $($type:ty),*) => {
{ {
if let Some(parser) = $crate::Parser::new($input, $pattern) { #[allow(clippy::all)]
let captures = parser.captures(); $crate::Captures::new($input, $pattern)
let mut iter = captures.iter(); .and_then(|c| {
let mut iter = c.as_inner().iter();
Some(($(iter.next().unwrap().parse::<$type>()),*)) Some(($(iter.next()?.parse::<$type>().ok()?),*))
} else { })
None }
};
} }
/// Parse an input and template, and convert the captures to the specified types.
///
/// ## Panics
///
/// This macro unwraps the parse result, causing a panic in any of the following cases:
/// - The template does not match the input.
/// - The capture could not be converted to the specified type.
///
/// ## Examples
///
/// Basic usage:
/// ```
/// # #[macro_use] extern crate eyes;
/// # fn main() {
/// let (a, b, c) = eyes::parse!("1 2,3", "{} {},{}", u8, u8, u8);
/// assert!(a == 1 && b == 2 && c == 3);
/// # }
/// ```
#[macro_export]
macro_rules! parse {
($input: expr, $pattern: tt, $($type:ty),*) => {
{
$crate::try_parse!($input, $pattern, $($type),*).unwrap()
} }
}; };
} }
@ -133,8 +183,8 @@ mod tests {
println!("p2: {:?}", (&x2, &y2)); println!("p2: {:?}", (&x2, &y2));
assert_eq!( assert_eq!(
(op.unwrap().as_str(), x1, y1, x2, y2), (op.as_str(), x1, y1, x2, y2),
("turn off", Ok(660), Ok(55), Ok(986), Ok(197)) ("turn off", 660, 55, 986, 197)
); );
} }
@ -152,6 +202,37 @@ mod tests {
println!("b: {:?}", b); println!("b: {:?}", b);
println!("c: {:?}", c); println!("c: {:?}", c);
assert_eq!((a, b, c), (Ok(775), Ok(785), Ok(361))); assert_eq!((a, b, c), (775, 785, 361));
}
#[test]
fn short_input() {
let input = "1x1";
let template = "{}x{}";
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let (a, b) = try_parse!(input, template, usize, usize).unwrap();
println!("a: {:?}", a);
println!("b: {:?}", b);
assert_eq!((a, b), (1, 1))
}
#[test]
fn match_whole_input() {
let input = "3240955";
let template = "{}";
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let a = try_parse!(input, template, usize).unwrap();
println!("a: {:?}", a);
assert_eq!(a, 3240955)
} }
} }

View file

@ -1,16 +0,0 @@
use eyes::*;
fn main() {
let input = "turn off 660,55 through 986,197";
let template = "{} {},{} through {},{}";
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let (op, x1, y1, x2, y2) =
try_parse!(input, template, String, usize, usize, usize, usize).unwrap();
println!("op: {:?}", op);
println!("p1: {:?}", (x1, y1));
println!("p2: {:?}", (x2, y2));
}