jarq 0.2.1

An interactive jq-like JSON query tool with a TUI
Documentation
//! Primitive parsing: integers, string literals, identifiers

use nom::{
    character::complete::{char, digit1},
    combinator::{opt, recognize},
    Parser,
};

pub fn parse_integer(input: &str) -> nom::IResult<&str, i64> {
    let (input, s) = recognize(|i| {
        let (i, _) = opt(char('-')).parse(i)?;
        digit1(i)
    })
    .parse(input)?;

    let n: i64 = s.parse().map_err(|_| {
        nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
    })?;

    Ok((input, n))
}

pub fn parse_string_literal(input: &str) -> nom::IResult<&str, String> {
    let (input, _) = char('"').parse(input)?;

    let mut result = String::new();
    let mut chars = input.chars();
    let mut consumed = 0;

    loop {
        match chars.next() {
            None => {
                return Err(nom::Err::Error(nom::error::Error::new(
                    input,
                    nom::error::ErrorKind::Char,
                )));
            }
            Some('"') => {
                consumed += 1;
                break;
            }
            Some('\\') => {
                consumed += 1;
                match chars.next() {
                    None => {
                        return Err(nom::Err::Error(nom::error::Error::new(
                            input,
                            nom::error::ErrorKind::Char,
                        )));
                    }
                    Some('n') => {
                        result.push('\n');
                        consumed += 1;
                    }
                    Some('r') => {
                        result.push('\r');
                        consumed += 1;
                    }
                    Some('t') => {
                        result.push('\t');
                        consumed += 1;
                    }
                    Some('\\') => {
                        result.push('\\');
                        consumed += 1;
                    }
                    Some('"') => {
                        result.push('"');
                        consumed += 1;
                    }
                    Some('/') => {
                        result.push('/');
                        consumed += 1;
                    }
                    Some('b') => {
                        result.push('\u{0008}');
                        consumed += 1;
                    }
                    Some('f') => {
                        result.push('\u{000C}');
                        consumed += 1;
                    }
                    Some('u') => {
                        consumed += 1;
                        let mut hex = String::new();
                        for _ in 0..4 {
                            match chars.next() {
                                Some(c) if c.is_ascii_hexdigit() => {
                                    hex.push(c);
                                    consumed += c.len_utf8();
                                }
                                _ => {
                                    return Err(nom::Err::Error(nom::error::Error::new(
                                        input,
                                        nom::error::ErrorKind::HexDigit,
                                    )));
                                }
                            }
                        }
                        let code = u32::from_str_radix(&hex, 16).map_err(|_| {
                            nom::Err::Error(nom::error::Error::new(
                                input,
                                nom::error::ErrorKind::HexDigit,
                            ))
                        })?;
                        let c = char::from_u32(code).ok_or_else(|| {
                            nom::Err::Error(nom::error::Error::new(
                                input,
                                nom::error::ErrorKind::Char,
                            ))
                        })?;
                        result.push(c);
                    }
                    Some(_) => {
                        return Err(nom::Err::Error(nom::error::Error::new(
                            input,
                            nom::error::ErrorKind::Char,
                        )));
                    }
                }
            }
            Some(c) => {
                result.push(c);
                consumed += c.len_utf8();
            }
        }
    }

    Ok((&input[consumed..], result))
}

pub fn identifier(input: &str) -> nom::IResult<&str, String> {
    let mut chars = input.chars();
    let first = chars.next().ok_or_else(|| {
        nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Alpha))
    })?;

    if !first.is_alphabetic() && first != '_' {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Alpha,
        )));
    }

    let mut len = first.len_utf8();
    for c in chars {
        if c.is_alphanumeric() || c == '_' {
            len += c.len_utf8();
        } else {
            break;
        }
    }

    Ok((&input[len..], input[..len].to_string()))
}