proj4wkt 0.1.1

Parse WKT to Proj strings
Documentation
//!
//! Generic, grammar free WKT parser implementation
//!
//! The parser does not perform allocation and generates
//! tokens that reference str slices of the original input
//!
use nom::{
    branch::alt,
    bytes::complete::{is_not, tag},
    character::complete::{alpha1, alphanumeric1, char, digit1, multispace0},
    combinator::{all_consuming, cut, fail, iterator, map, recognize},
    multi::{fold_many0, many0_count},
    number::complete::recognize_float,
    sequence::{delimited, pair, preceded, terminated},
    IResult, Parser,
};

use crate::errors::{Error, Result};
use crate::log;

use std::fmt::Debug;

/// Parsed WKT attributes
#[derive(Debug, PartialEq)]
pub enum Attribute<'a, T> {
    /// A quoted string
    Quoted(&'a str),
    /// A number
    Number(&'a str),
    /// A label
    Label(&'a str),
    /// A WKT keyword with associated
    /// processor output
    Keyword(&'a str, T),
}

/// A recursive token generator processor
///
/// A `Processor` implements the grammar and produce the syntactic tree from
/// parsed attributes
pub trait Processor<'a> {
    type Err: Debug;
    type Output;

    /// Process a WKT node with key `key` and childs attributs `attrs`.
    ///
    /// Attributes are lazily parsed during iteration by calling
    /// `process` recursively
    ///
    /// Note that the `attrs` iterator must be totally consummed otherwise and not
    /// be called after completion or it will panic due to `nom` implementation details.
    fn process<I>(&self, key: &'a str, depth: usize, attrs: I) -> Result<Self::Output, Self::Err>
    where
        I: Iterator<Item = Attribute<'a, Self::Output>>;
}

/// Parse WKT string
///
/// Parse a WKT string by applying a [`Processor`]
/// to WKT nodes
pub fn parse<'a, P, O>(i: &'a str, p: &P) -> Result<O>
where
    P: Processor<'a, Output = O>,
{
    all_consuming(|i: &'a str| object(i, p, 0))
        .parse(i)
        .map_err(|_| Error::Parse)
        .map(|(_, value)| match value {
            Attribute::Keyword(_, out) => out,
            _ => unreachable!(),
        })
}

// Double quote delimited string
fn quoted_string<'a>(i: &'a str) -> IResult<&'a str, &'a str> {
    delimited(
        char('"'),
        |s: &'a str| {
            map(
                fold_many0(
                    alt((tag("\"\""), is_not("\""))),
                    || 0,
                    |n, item: &str| n + item.len(),
                ),
                |len| &s[..len],
            )
            .parse(s)
        },
        char('"'),
    )
    .parse(i)
}

// Number
fn number(i: &str) -> IResult<&str, &str> {
    alt((recognize_float, recognize(digit1))).parse(i)
}

fn keyword(i: &str) -> IResult<&str, &str> {
    recognize(pair(
        alt((alpha1, tag("_"))),
        many0_count(alt((alphanumeric1, tag("_")))),
    ))
    .parse(i)
}

fn log_failure<E: Debug, T>(_err: E) -> IResult<&'static str, T> {
    log::error!("Wkt failure {_err:?}");
    cut(fail()).parse("")
}

// Process object attribute
fn object<'a, P, O>(i: &'a str, p: &P, depth: usize) -> IResult<&'a str, Attribute<'a, O>>
where
    P: Processor<'a, Output = O>,
{
    terminated(keyword, trim_left(char('[')))
        .parse(i.trim_start())
        .and_then(|(rest, key)| {
            attribute_list(rest, p, depth, key).and_then(|(rest, node)| {
                match cut(trim_left(char(']'))).parse(rest) {
                    Ok((rest, _)) => Ok((rest, node)),
                    Err(err) => {
                        log::error!("Missing closing delimiter for {key}");
                        Err(err)
                    }
                }
            })
        })
}

// Parse attributes list
// attribute_list:
//      attribute_list COMMA attribute
//    | attribute
fn attribute_list<'a, P, O>(
    i: &'a str,
    p: &P,
    depth: usize,
    key: &'a str,
) -> IResult<&'a str, Attribute<'a, O>>
where
    P: Processor<'a, Output = O>,
{
    let (rest, attr) = attribute(i, p, depth)?;

    let mut it = iterator(
        rest,
        preceded(trim_left(char(',')), |i: &'a str| attribute(i, p, depth)),
    );

    match p.process(key, depth, std::iter::once(attr).chain(&mut it)) {
        Ok(node) => {
            let (rest, _) = it.finish()?;
            Ok((rest, Attribute::Keyword(key, node)))
        }
        Err(err) => log_failure(err),
    }
}

// Parse Node
// attribute:
//     keyword attibute_list
//   | quoted_string
//   | number
fn attribute<'a, P, O>(i: &'a str, p: &P, depth: usize) -> IResult<&'a str, Attribute<'a, O>>
where
    P: Processor<'a, Output = O>,
{
    let i = i.trim_start();
    object(i, p, depth + 1)
        .or_else(|_| map(quoted_string, |s| Attribute::Quoted(s)).parse(i))
        .or_else(|_| map(number, |n| Attribute::Number(n)).parse(i))
        .or_else(|_| map(keyword, |l| Attribute::Label(l)).parse(i))
}

// Trim whitespaces
#[inline]
pub(super) fn trim_left<
    'a,
    O,
    E: nom::error::ParseError<&'a str>,
    F: nom::Parser<&'a str, Output = O, Error = E>,
>(
    f: F,
) -> impl nom::Parser<&'a str, Output = O, Error = E> {
    preceded(multispace0, f)
}

// ==============================
//  Tests
// ==============================
#[cfg(test)]
mod tests {
    use super::*;
    use crate::errors::{Error, Result};

    #[test]
    fn parse_quoted_str() {
        assert_eq!(quoted_string(r#""""#), Ok(("", "")));
        assert_eq!(quoted_string(r#""foo""bar""#), Ok(("", r#"foo""bar"#)));
        assert_eq!(quoted_string(r#""foobar" baz"#), Ok((" baz", r#"foobar"#)));
    }

    #[test]
    fn parse_number() {
        assert_eq!(number("1234.56"), Ok(("", "1234.56")));
        assert_eq!(number("1234"), Ok(("", "1234")));
        assert!(number("baz").is_err());
    }

    #[test]
    fn parse_keyword() {
        assert_eq!(keyword("KEY"), Ok(("", "KEY")));
        assert_eq!(keyword("KEY12_"), Ok(("", "KEY12_")));
        assert_eq!(keyword("_KEY1"), Ok(("", "_KEY1")));
        assert_eq!(keyword("_1KEY"), Ok(("", "_1KEY")));
        assert!(keyword("1KEY").is_err());
    }

    #[derive(Debug, PartialEq)]
    struct Node<'a>(&'a str, Vec<Attribute<'a, Node<'a>>>);

    #[derive(Default)]
    struct Builder;

    impl<'a> Processor<'a> for Builder {
        type Err = Error;
        type Output = Node<'a>;

        fn process<I>(
            &self,
            key: &'a str,
            _depth: usize,
            attrs: I,
        ) -> Result<Self::Output, Self::Err>
        where
            I: Iterator<Item = Attribute<'a, Self::Output>>,
        {
            Ok(Node(key, attrs.collect()))
        }
    }

    #[test]
    fn parse_wkt() {
        let wkt = parse(r#"FOO["foo", BAR["bar"], baz]"#, &Builder::default()).unwrap();

        assert_eq!(
            wkt,
            Node(
                "FOO",
                vec![
                    Attribute::Quoted("foo"),
                    Attribute::Keyword("BAR", Node("BAR", vec![Attribute::Quoted("bar")])),
                    Attribute::Label("baz"),
                ],
            ),
        );
    }

    #[test]
    fn parse_failure() {
        let mut wkt = Builder::default();
        assert!(parse(r#"FOO["foo", BAR["bar"]"#, &mut wkt).is_err());
    }
}