#![doc = include_str!("../README.md")]
use crate::peekable2::{Peekable2, Peekable2Ext};
use std::collections::HashMap;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::str::CharIndices;
mod peekable2;
pub type Key = String;
pub type Value = String;
pub type Params = HashMap<Key, Value>;
pub struct ConfStr {
service: String,
params: Params,
}
impl Debug for ConfStr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "ConfStr {{ service: {:?}, .. }}", self.service)
}
}
impl ConfStr {
pub fn new(service: String, params: Params) -> Self {
ConfStr { service, params }
}
pub fn service(&self) -> &str {
&self.service
}
pub fn params(&self) -> &Params {
&self.params
}
pub fn get(&self, key: &str) -> Option<&str> {
self.params.get(key).map(|s| s.as_str())
}
}
pub type Position = usize;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ErrorKind {
ExpectedIdentifierNot(char),
MustBeAlphanumeric(char),
ExpectedIdentifierNotEmpty,
BadSeparator((char, char)),
IncompleteKeyValue,
InvalidCharInValue(char),
DuplicateKey(String),
}
impl<'a> PartialEq<&'a ErrorKind> for ErrorKind {
fn eq(&self, other: &&'a ErrorKind) -> bool {
self == *other
}
}
impl PartialEq<ErrorKind> for &ErrorKind {
fn eq(&self, other: &ErrorKind) -> bool {
*self == other
}
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ErrorKind::ExpectedIdentifierNot(c) => {
write!(
f,
"expected identifier to start with ascii letter, not {:?}",
c
)
}
ErrorKind::MustBeAlphanumeric(c) => write!(f, "must be alphanumeric, not {:?}", c),
ErrorKind::ExpectedIdentifierNotEmpty => {
write!(f, "expected identifier, not an empty string")
}
ErrorKind::BadSeparator((e, c)) => {
write!(f, "bad separator, expected {:?} got {:?}", e, c)
}
ErrorKind::IncompleteKeyValue => {
write!(f, "incomplete key-value pair before end of input")
}
ErrorKind::InvalidCharInValue(c) => write!(f, "invalid char {:?} in value", c),
ErrorKind::DuplicateKey(s) => write!(f, "duplicate key {:?}", s),
}
}
}
#[derive(Debug)]
pub struct ParsingError {
kind: ErrorKind,
position: usize,
}
impl ParsingError {
pub fn position(&self) -> usize {
self.position
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
}
fn parse_err(kind: ErrorKind, position: Position) -> ParsingError {
ParsingError { kind, position }
}
impl Display for ParsingError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{} at position {}", self.kind, self.position)
}
}
impl std::error::Error for ParsingError {}
fn parse_ident(
iter: &mut Peekable2<CharIndices>,
next_pos: &mut Position,
) -> Result<Key, ParsingError> {
let mut token = String::new();
while let Some((pos, c)) = iter.peek0() {
*next_pos = *pos;
if c.is_ascii_alphanumeric() || *c == '_' {
token.push(*c);
iter.next();
} else {
if token.is_empty() {
return Err(parse_err(ErrorKind::ExpectedIdentifierNot(*c), *next_pos));
} else if !c.is_ascii() || matches!(c, '\0'..=' ') {
return Err(parse_err(ErrorKind::MustBeAlphanumeric(*c), *next_pos));
}
break;
}
}
if token.is_empty() {
return Err(parse_err(ErrorKind::ExpectedIdentifierNotEmpty, *next_pos));
}
Ok(token)
}
fn parse_value(
iter: &mut Peekable2<CharIndices>,
next_pos: &mut Position,
) -> Result<Value, ParsingError> {
let mut value = String::new();
loop {
let c1 = iter.peek0().cloned();
let c2 = iter.peek1().cloned();
if let Some((p, _)) = c1 {
*next_pos = p;
}
match (c1, c2) {
(Some((_, ';')), Some((_, ';'))) => {
let _ = iter.next();
let _ = iter.next();
value.push(';');
}
(Some((_, ';')), _) => break,
(Some((p, c)), _) => {
if matches!(c, '\u{0}'..='\u{1f}' | '\u{7f}'..='\u{9f}') {
return Err(parse_err(ErrorKind::InvalidCharInValue(c), p));
}
value.push(c);
let _ = iter.next();
}
(None, _) => break,
}
}
Ok(value)
}
fn parse_double_colon(
iter: &mut Peekable2<CharIndices>,
next_pos: &mut Position,
) -> Result<bool, ParsingError> {
let c1 = iter.next();
let c2 = iter.next();
match (c1, c2) {
(Some((_, ':')), Some((_, ':'))) => {
*next_pos += 2;
Ok(true)
}
(None, None) => Ok(false),
(Some((_, ':')), Some((p, c))) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
(Some((p, c)), _) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
(None, _) => unreachable!("peekable2 guarantees that the second item is always None"),
}
}
fn parse_params(
iter: &mut Peekable2<CharIndices>,
next_pos: &mut Position,
input_len: usize,
) -> Result<Params, ParsingError> {
let mut params = Params::new();
while let Some((p, _)) = iter.peek0() {
*next_pos = *p;
let key_pos = *next_pos;
let key = parse_ident(iter, next_pos)?;
if params.contains_key(&key) {
return Err(parse_err(ErrorKind::DuplicateKey(key.clone()), key_pos));
}
match iter.next() {
Some((p, '=')) => *next_pos = p + 1,
Some((p, c)) => return Err(parse_err(ErrorKind::BadSeparator(('=', c)), p)),
None => return Err(parse_err(ErrorKind::IncompleteKeyValue, input_len)),
}
let value = parse_value(iter, next_pos)?;
iter.next(); params.insert(key, value);
}
Ok(params)
}
pub fn parse_conf_str(input: &str) -> Result<ConfStr, ParsingError> {
let mut iter = input.char_indices().peekable2();
let mut next_pos = 0;
let service = parse_ident(&mut iter, &mut next_pos)?;
let has_separator = parse_double_colon(&mut iter, &mut next_pos)?;
if !has_separator {
return Ok(ConfStr::new(service, Params::new()));
}
let params = parse_params(&mut iter, &mut next_pos, input.len())?;
Ok(ConfStr::new(service, params))
}