#input #stdin #parse #read #parser

scan-rules

This crate provides some macros for quickly parsing values out of text. Roughly speaking, it does the inverse of the print!/format! macros; or, in other words, a similar job to scanf from C.

8 releases

Uses old Rust 2015

0.2.0 Nov 7, 2016
0.1.3 Apr 23, 2016
0.1.1 Feb 5, 2016
0.0.4 Jan 22, 2016

#1341 in Rust patterns

Download history 44/week @ 2023-11-15 51/week @ 2023-11-22 47/week @ 2023-11-29 43/week @ 2023-12-06 43/week @ 2023-12-13 47/week @ 2023-12-20 18/week @ 2023-12-27 30/week @ 2024-01-03 60/week @ 2024-01-10 44/week @ 2024-01-17 53/week @ 2024-01-24 43/week @ 2024-01-31 222/week @ 2024-02-07 56/week @ 2024-02-14 59/week @ 2024-02-21 71/week @ 2024-02-28

417 downloads per month
Used in 7 crates

MIT/Apache

305KB
5K SLoC

scan-rules

This crate provides some macros for quickly parsing values out of text. Roughly speaking, it does the inverse of the print!/format! macros; or, in other words, a similar job to scanf from C.

The macros of interest are:

  • readln! - reads and scans a line from standard input.
  • try_readln! - like readln!, except it returns a Result instead of panicking.
  • scan! - scans the provided string.

Plus two convenience macros:

  • let_scan! - scans a string and binds captured values directly to local variables. Only supports one pattern and panics if it doesn't match.
  • let_scanln! - reads and scans a line from standard input, binding captured values directly to local variables. Only supports one pattern and panics if it doesn't match.

If you are interested in implementing support for your own types, see the ScanFromStr trait.

The available abstract scanners can be found in the scanner module.

Links

Compatibility

scan-rules is compatible with rustc version 1.6.0 and higher.

  • Due to a breaking change, scan-rules is not compatible with regex version 0.1.66 or higher.

  • rustc < 1.10 will not have the let_scanln! macro.

  • rustc < 1.7 will have only concrete implementations of ScanFromStr for the Everything, Ident, Line, NonSpace, Number, Word, and Wordish scanners for &str and String output types. 1.7 and higher will have generic implementations for all output types such that &str: Into<Output>.

  • rustc < 1.6 is explicitly not supported, due to breaking changes in Rust itself.

Quick Examples

Here is a simple CLI program that asks the user their name and age. You can run this using cargo run --example ask_age.

#[macro_use] extern crate scan_rules;

use scan_rules::scanner::Word;

fn main() {
    print!("What's your name? ");
    let name: String = readln! { (let name: Word<String>) => name };
    //                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ rule
    //                                                       ^~~^ body
    //                           ^~~~~~~~~~~~~~~~~~~~~~~^ pattern
    //                            ^~~~~~~~~~~~~~~~~~~~~^ variable binding

    print!("Hi, {}.  How old are you? ", name);
    readln! {
        (let age) => {
    //   ^~~~~~^ implicitly typed variable binding
            let age: i32 = age;
            println!("{} years old, huh?  Neat.", age);
        },
        (..other) => println!("`{}` doesn't *look* like a number...", other),
    //   ^~~~~~^ bind to any input "left over"
    }

    print!("Ok.  What... is your favourite colour? (R, G, B): ");
    let_scanln!(let r: f32, ",", let g: f32, ",", let b: f32);
    //          ^~~~^            ^~~~^            ^~~~^
    // Scans and binds three variables without nesting scope.
    // Panics if *anything* goes wrong.
    if !(g < r && g < b && b >= r * 0.25 && b <= r * 0.75) {
        println!("Purple's better.");
    } else {
        println!("Good choice!");
    }
}

This example shows how to parse one of several different syntaxes. You can run this using cargo run --example scan_data.

#[macro_use] extern crate scan_rules;

use std::collections::BTreeSet;

// `Word` is an "abstract" scanner; rather than scanning itself, it scans some
// *other* type using custom rules.  In this case, it scans a word into a
// string slice.  You can use `Word<String>` to get an owned string.
use scan_rules::scanner::Word;

#[derive(Debug)]
enum Data {
    Vector(i32, i32, i32),
    Truthy(bool),
    Words(Vec<String>),
    Lucky(BTreeSet<i32>),
    Other(String),
}

fn main() {
    print!("Enter some data: ");
    let data = readln! {
        ("<", let x, ",", let y, ",", let z, ">") => Data::Vector(x, y, z),
    //      ^ pattern terms are comma-separated
    //   ^~^ literal text match

        // Rules are tried top-to-bottom, stopping as soon as one matches.
        (let b) => Data::Truthy(b),
        ("yes") => Data::Truthy(true),
        ("no") => Data::Truthy(false),

        ("words:", [ let words: Word<String> ],+) => Data::Words(words),
    //             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~^ repetition pattern
    //                                         ^ one or more matches
    //                                        ^ matches must be comma-separated

        ("lucky numbers:", [ let ns: i32 ]*: BTreeSet<_>) => Data::Lucky(ns),
    //          collect into specific type ^~~~~~~~~~~~^
    //                                    ^ zero or more (you might be unlucky!)
    //                                      (no separator this time)

        // Rather than scanning a sequence of values and collecting them into
        // a `BTreeSet`, we can instead scan the `BTreeSet` *directly*.  This
        // scans the syntax `BTreeSet` uses when printed using `{:?}`:
        // `{1, 5, 13, ...}`.
        ("lucky numbers:", let ns) => Data::Lucky(ns),

        (..other) => Data::Other(String::from(other))
    };
    println!("data: {:?}", data);
}

This example demonstrates using runtime scanners and the let_scan! convenience macro. You can run this using cargo run --example runtime_scanners.

//! **NOTE**: requires the `regex` feature.
#[macro_use] extern crate scan_rules;

fn main() {
    use scan_rules::scanner::{
        NonSpace, Number, Word,             // static scanners
        max_width_a, exact_width_a, re_str, // runtime scanners
    };

    // Adapted example from <http://en.cppreference.com/w/cpp/io/c/fscanf>.
    let inp = "25 54.32E-1 Thompson 56789 0123 56ß水";

    // `let_scan!` avoids the need for indentation and braces, but only supports
    // a single pattern, and panics if anything goes wrong.
    let_scan!(inp; (
        let i: i32, let x: f32, let str1 <| max_width_a::<NonSpace>(9),
    //               use runtime scanner ^~~~~~~~~~~~~~~~~~~~~~~~~~~~^
    //          limit maximum width of a... ^~~~~~~~~~^
    //                      ...static NonSpace scanner... ^~~~~~~^
    //                                                      9 bytes ^
        let j <| exact_width_a::<i32>(2), let y: f32, let _: Number,
    //        ^~~~~~~~~~~~~~~~~~~~~~~~~^ scan an i32 with exactly 2 digits
        let str2 <| re_str(r"^[0-9]{1,3}"), let warr: Word
    //           ^~~~~~~~~~~~~~~~~~~~~~~~^ scan using a regular expression
    ));

    println!(
        "Converted fields:\n\
            i = {i:?}\n\
            x = {x:?}\n\
            str1 = {str1:?}\n\
            j = {j:?}\n\
            y = {y:?}\n\
            str2 = {str2:?}\n\
            warr = {warr:?}",
        i=i, j=j, x=x, y=y,
        str1=str1, str2=str2, warr=warr);
}

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~1.1–1.9MB
~39K SLoC