Installation

[dependencies] parsable = "0.1"

Example

Implementation of a basic operation interpreter that only works with positive integer and without operator priorities.

```rust use parsable::{parsable, Parsable, ParseOptions};

[parsable]

struct Operation { firstoperand: Operand, otheroperands: Vec<(Operator, Operand)> }

impl Operation { fn process(&self) -> i32 { let mut result = self.first_operand.process();

    for (operator, operand) in &self.other_operands {
        let value = operand.process();

        result = match operator {
            Operator::Plus => result + value,
            Operator::Minus => result - value,
            Operator::Mult => result * value,
            Operator::Div => result / value,
            Operator::Mod => result % value,
        }
    }

    result
}

}

[parsable]

enum Operand { Number(NumberLiteral), Wrapped(WrappedOperation) }

impl Operand { fn process(&self) -> i32 { match self { Operand::Number(number) => number.process(), Operand::Wrapped(wrapped) => wrapped.process(), } } }

[parsable]

struct NumberLiteral { #[parsable(regex=r"\d+")] value: String }

impl NumberLiteral { fn process(&self) -> i32 { self.value.parse().unwrap() } }

[parsable]

struct WrappedOperation { #[parsable(brackets="()")] operation: Box }

impl WrappedOperation { fn process(&self) -> i32 { self.operation.process() } }

[parsable]

enum Operator { Plus = "+", Minus = "-", Mult = "*", Div = "/", Mod = "%" }

fn main() { let operationstring = "3 + (4 * 5)".tostring(); let parse_options = ParseOptions::default();

match Operation::parse(operation_string, parse_options) {
    Ok(operation) => {
        println!("result: {}", operation.process());
    },
    Err(error) => {
        dbg!(error);
    }
}

} ```

The #[parsable] macro

Tagging a struct or enum with the #[parsable] macro implements the Parsable trait for the item, with the condition that all fields must also implement the Parsable trait. It can also be used on a field to tweak the way it is parsed.

Struct

Enum

```rust

[parsable]

enum MyOperation { BinaryOperation(NumerLiteral, Operator, NumerLiteral), Number(NumberLiteral), Zero = "zero" }

// If the first two variants are swapped, then the parsing will never reach the SimpleOperation variant ```

Builtin types

String

A string field must be tagged with the #[parsable(regex="<pattern>")] or #[parsable(value="<pattern>")] macro option to specify how to parse it.

```rust // Matches at least one digit

[parsable]

struct NumberLiteral { #[parsable(regex=r"\d+")] value: String } ```

```rust

[parsable]

// Only matches the string "+" struct PlusSign { #[parsable(value="+")] value: String } ```

Option<T>

Matches T. If it fails, returns None but the parsing of the field is still considered successful.

```rust

[parsable]

enum Sign { Plus = "+", Minus = "-" }

// Matches a number with an optional sign

[parsable]

struct NumberLiteral { sign: Option, #[parsable(regex=r"\d+")] value: String } ```

Vec<T>

Matches as many T as possible successively. The following options can be specified:

```rust // Matches a non-empty list of numbers separated by a comma

[parsable]

struct NumberList { #[parsable(separator=",", min=1)] numbers: Vec } ```

Other types

Running the parser

The Parsable trait provides the parse() method that takes two arguments: - content: String: the string to parse - options: ParseOptions: parse options

The ParseOptions type has the following fields:

The file_path and package_root_path fields are forwarded to the FileInfo struct and are never actually used by the library.

Blank characters (spaces, new lines and tabulations) are always ignored during parsing.

File info

The FileInfo structure is used accross the library. It has the following fields:

It also provides the following methods:

Item location

Tagging a struct with #[parsable] adds a location field of type ItemLocation with the following fields:

The Parsable also trait provides a location() method:

A way to prevent the panic is to wrap enums with unit variants in a structure:

```rust

[parsable]

enum Operator { Plus = "+", Minus = "-", Mult = "*", Div = "/", Mod = "%" }

[parsable]

struct WrappedOperator { operator: Operator }

fn main() { let string = "+".to_string(); let options = ParseOptions::default(); let result = WrappedOperator::parse(string, options).unwrap();

dbg!(result.location()); // It works!

} ```

Parse error

On failure, Parsable::parse() returns Err(ParseError). This structure has the following fields:

Macro options

Root attributes

```rust

[parsable(located=false)] // The location field will not be added

struct Operation { firstoperand: Operand, otheroperands: Vec<(Operator, Operand)> } ```

Field attributes

Manually implementing the Parsable trait

Sometimes #[parsable] is not enough and you want to implement your own parsing mechanism. This is done by implementing the parse_item, get_item_name and location methods.

```rust use parsable::{Parsable, StringReader};

struct MyInteger { value: u32, location: ItemLocation, }

impl Parsable for MyInteger { fn parseitem(reader: &mut StringReader) -> Option { let start = reader.getindex();

    match reader.read_regex(r"\d+") {
        Some(string) => Some(MyInteger {
            value: string.parse().unwrap(),
            location: reader.get_item_location(start),
        }),
        None => None,
    }
}

// Only used in errors
fn get_item_name() -> String {
    "integer".to_string()
}

// Not required, but convenient
fn location(&self) -> &ItemLocation {
    &self.location
}

}

fn main() { let numberstring = "56"; let number = MyInteger::parse(numberstring.to_string(), ParseOptions::default()).unwrap(); println!("{}", number.value); } ```

StringReader wraps the string being parsed with an index that increases as the parsing goes on. It has the following methods:

If parse_item returns None, it must ensure that the index is the same when the function exits as it was when it started.

License

MIT