#![allow(clippy::tabs_in_doc_comments)]
pub mod config;
pub mod parser;
pub mod scanner;
pub use config::Config;
use scanner::{Span, TokenKind};
use std::str::Utf8Error;
use std::string::FromUtf8Error;
pub fn parse_config(content: &str) -> Result<Config, NcclError> {
let mut scanner = scanner::Scanner::new(content);
parser::parse(&mut scanner)
}
pub fn parse_config_with<'a>(
config: &Config<'a>,
content: &'a str,
) -> Result<Config<'a>, NcclError> {
let mut scanner = scanner::Scanner::new(content);
parser::parse_with(&mut scanner, config)
}
#[derive(Debug, PartialEq)]
pub enum NcclError {
UnexpectedToken {
span: Span,
expected: TokenKind,
got: TokenKind,
},
UnterminatedString {
start: usize,
},
TrailingCharacters {
line: usize,
},
ScanUnknownEscape {
line: usize,
column: usize,
escape: char,
},
ParseUnknownEscape {
escape: char,
},
Utf8 {
err: Utf8Error,
},
}
impl std::fmt::Display for NcclError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NcclError::UnexpectedToken {
span,
expected,
got,
} => write!(
f,
"expected {:?}, got {:?} at {}:{}",
expected, got, span.line, span.column,
),
NcclError::UnterminatedString { start } => {
write!(f, "unterminated string starting on line {}", start)
}
NcclError::TrailingCharacters { line } => {
write!(f, "characters after string on line {}", line)
}
NcclError::ScanUnknownEscape {
escape,
line,
column,
} => write!(f, "unknown escape {:?} at {}:{}", escape, line, column),
NcclError::ParseUnknownEscape { escape } => write!(f, "unknown escape {:?}", escape),
NcclError::Utf8 { err } => write!(f, "{}", err),
}
}
}
impl From<Utf8Error> for NcclError {
fn from(err: Utf8Error) -> Self {
NcclError::Utf8 { err }
}
}
impl From<FromUtf8Error> for NcclError {
fn from(err: FromUtf8Error) -> Self {
NcclError::Utf8 {
err: err.utf8_error(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::read_to_string;
#[test]
fn index() {
let content = read_to_string("examples/config.nccl").unwrap();
let config = parse_config(&content).unwrap();
assert_eq!(config["server"]["root"].value(), Some("/var/www/html"));
}
#[test]
fn values() {
let content = read_to_string("examples/config.nccl").unwrap();
let config = parse_config(&content).unwrap();
assert_eq!(
vec![80, 443],
config["server"]["port"]
.values()
.map(|port| port.parse::<u16>())
.collect::<Result<Vec<u16>, _>>()
.unwrap()
);
}
#[test]
fn value() {
let content = read_to_string("examples/long.nccl").unwrap();
let config = parse_config(&content).unwrap();
assert_eq!(config["bool too"].value().unwrap(), "false");
}
#[test]
fn duplicates() {
let content = read_to_string("examples/duplicates.nccl").unwrap();
let config = parse_config(&content).unwrap();
assert_eq!(
config["something"].values().collect::<Vec<_>>(),
vec!["with", "duplicates"]
);
}
#[test]
fn duplicates2() {
let content1 = read_to_string("examples/duplicates.nccl").unwrap();
let config1 = parse_config(&content1).unwrap();
let content2 = read_to_string("examples/duplicates2.nccl").unwrap();
let config2 = parse_config_with(&config1, &content2).unwrap();
assert_eq!(2, config2["something"].values().collect::<Vec<_>>().len());
}
#[test]
fn duplicates3() {
let content1 = read_to_string("examples/dup3.nccl").unwrap();
let config1 = parse_config(&content1).unwrap();
assert_eq!(
vec!["oh christmas tree", "o tannenbaum", "five golden rings"],
config1["oh christmas tree"].values().collect::<Vec<_>>()
);
}
#[test]
fn inherit() {
let sc = read_to_string("examples/inherit.nccl").unwrap();
let uc = read_to_string("examples/inherit2.nccl").unwrap();
let schema = parse_config(&sc).unwrap();
let user = parse_config_with(&schema, &uc).unwrap();
assert_eq!(3, user["hello"]["world"].values().collect::<Vec<_>>().len());
assert_eq!(
3,
user["sandwich"]["meat"].values().collect::<Vec<_>>().len()
);
}
#[test]
fn comments() {
let config = r#"x
# comment
something
# comment again
bingo
does this work?
who knows
# I sure don't
is this a child?
"#;
let config = parse_config(config).unwrap();
assert_eq!(config["x"]["something"].value().unwrap(), "bingo");
assert!(config["does this work?"].has_value("who knows"));
assert!(config["does this work?"].has_value("is this a child?"));
}
#[test]
fn all_of_em() {
let source = read_to_string("examples/all-of-em.nccl").unwrap();
let mut scanner = scanner::Scanner::new(&source);
let config = parser::parse(&mut scanner).unwrap();
assert_eq!(
Ok(vec![
String::from("i # j"),
String::from("k"),
String::from("m")
]),
config["h"]
.children()
.map(|config| config.parse_quoted())
.collect::<Result<Vec<_>, _>>()
);
assert_eq!(Some(scanner::QuoteKind::Double), config["h"]["k"].quotes);
assert_eq!(Some(scanner::QuoteKind::Single), config["h"]["m"].quotes);
}
#[test]
fn escapes() {
let config = read_to_string("examples/escapes.nccl").unwrap();
let config = parse_config(&config).unwrap();
assert_eq!(
config["hello"].child().unwrap().parse_quoted().unwrap(),
"people of the earth\nhow's it doing?\""
);
assert_eq!(
config["hello"].child().unwrap().quotes,
Some(scanner::QuoteKind::Double)
);
}
#[test]
fn quote() {
let config = read_to_string("examples/quote.nccl").unwrap();
let config = parse_config(&config).unwrap();
assert_eq!(config["howdy"].values().collect::<Vec<_>>(), vec!["hello"]);
}
#[test]
fn fuzz() {
let dir = std::fs::read_dir("examples/fuzz/scan").unwrap();
for entry in dir {
let entry = entry.unwrap();
if entry
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("err")
{
println!("check scan bad: {}", entry.path().display());
let source = std::fs::read_to_string(entry.path()).unwrap();
let result = scanner::Scanner::new(&source).scan_all();
println!(" {:?}", result);
result.unwrap_err();
} else {
println!("check scan good: {}", entry.path().display());
let source = std::fs::read_to_string(entry.path()).unwrap();
let result = scanner::Scanner::new(&source).scan_all();
result.unwrap();
}
}
let dir = std::fs::read_dir("examples/fuzz/parse").unwrap();
for entry in dir {
let entry = entry.unwrap();
if entry
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("err")
{
println!("check parse bad: {}", entry.path().display());
let source = std::fs::read_to_string(entry.path()).unwrap();
let result = parse_config(&source);
println!(" {:#?}", result);
result.unwrap_err();
} else {
println!("check parse good: {}", entry.path().display());
let source = std::fs::read_to_string(entry.path()).unwrap();
let result = parse_config(&source);
println!(" {:#?}", result);
result.unwrap();
}
}
}
}